Skip to main content

Extending a Live Component in Sylius 2.0

· 3 min read
Stephan Hochdörfer
Head of IT Business Operations

A while ago, I covered how you can extend a Sylius Core Twig Component thanks to the Symfony\UX\TwigComponent\Event\PreRenderEvent event. What about Live Components of Sylius core? Can we extend them as well?

Yes is the short answer. Since Live Components are based on Twig Components, the same rules apply. We can hook into the very same PreRenderEvent event, but then it gets a bit messy.

What do I want to achieve? Every time the generateProductSlug method of the class Sylius\Bundle\AdminBundle\Twig\Component\Product\FormComponent is invoked, I want to set additional data to influence the rendering of the form.

The basic structure of our event subscriber class is similar to the one we used for the Twig Component:

<?php

declare(strict_types=1);

namespace BitExpert\MySyliusPlugin\Twig\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;

class ProductFormComponentSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [PreRenderEvent::class => 'onPreRender'];
}

public function onPreRender(PreRenderEvent $event): void
{
}
}

Also, the class needs to be registered as a service:

<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="bitexpert.my_sylius_plugin.twig.price_component" class="BitExpert\MySyliusPlugin\Twig\EventSubscriber\ProductFormComponentSubscriber">
<tag name="kernel.event_subscriber" />
</service>

</services>
</container>

As we learned in the Twig Component blogpost, we know which component gets rendered and can ignore the Twig Components we don't care about like this:

public function onPreRender(PreRenderEvent $event): void
{
$component = $event->getComponent();
if (!$component instanceof FormComponent) {
return;
}
}

How do we know the generateProductSlug gets invoked? This is where it gets a bit messy. The only hackish solution I came up with is to check the request URI for the method name:

<?php

declare(strict_types=1);

namespace BitExpert\MySyliusPlugin\Twig\EventSubscriber;

use Sylius\Bundle\AdminBundle\Twig\Component\Product\FormComponent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;

class ProductFormComponentSubscriber implements EventSubscriberInterface
{
public function __construct(private RequestStack $requestStack)
{
}

public static function getSubscribedEvents(): array
{
return [PreRenderEvent::class => 'onPreRender'];
}

public function onPreRender(PreRenderEvent $event): void
{
$component = $event->getComponent();
if (!$component instanceof FormComponent) {
return;
}

$request = $this->requestStack->getMainRequest();
if (($request === null) || !str_ends_with($request->getRequestUri(), '/generateProductSlug')) {
return;
}
}
}

We must also access the $localeCode parameter that the generateProductSlug method call requires to solve our task. Again, we have to use a bit of a hackish way to extract the data, which means using json_decode() on the data request parameter:

<?php

declare(strict_types=1);

namespace BitExpert\MySyliusPlugin\Twig\EventSubscriber;

use Sylius\Bundle\AdminBundle\Twig\Component\Product\FormComponent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;

class ProductFormComponentSubscriber implements EventSubscriberInterface
{
public function __construct(private RequestStack $requestStack)
{
}

public static function getSubscribedEvents(): array
{
return [PreRenderEvent::class => 'onPreRender'];
}

public function onPreRender(PreRenderEvent $event): void
{
$component = $event->getComponent();
if (!$component instanceof FormComponent) {
return;
}

$request = $this->requestStack->getMainRequest();
if (($request === null) || !str_ends_with($request->getRequestUri(), '/generateProductSlug')) {
return;
}

$data = (array) json_decode((string)$request->request->get('data', ''), true);
/** @var string $localeCode */
$localeCode = $data['args']['localeCode'] ?? '';
if ($localeCode === '') {
return;
}
}
}

Last, we can use the $event->getVariables() method call to get all the variables, set whatever needs to be set, and let the Twig rendering do the rest.

I wished for a better way of accomplishing the task, ideally with a separate event that would expose the method name and parameters instead of having to manually parse them.

Still, I like the solution as it does not require extending or overwriting the core Sylius classes and potentially breaking future version upgrades.