Extending a Live Component in Sylius 2.0
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.