Skip to main content

Extending a Twig Component in Sylius 2.0

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

Sylius 2.0 uses Twig Components to create reusable, object-oriented templates. In a recent project, I needed to add information to the sylius_shop.twig.component.product.price component and was wondering how to achieve this best.

At first, I wasn't sure how to best approach this task. Does it make sense to extend the PHP class of the Twig Component? Or is there any better way of doing it? Ideally, it should be done in a way that is not too intrusive, making future updates less painful than needed.

After reading the docs, I realized I can hook into the Twig Component rendering process by subscribing to an event. I decided to try it and subscribe to the Symfony\UX\TwigComponent\Event\PreRenderEvent event.

This is how my basic PriceComponentSubscriber class looks like for a start:

<?php

declare(strict_types=1);

namespace BitExpert\MySyliusPlugin\Twig\EventSubscriber;

use Sylius\Bundle\ShopBundle\Twig\Component\Product\PriceComponent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\UX\TwigComponent\Event\PreRenderEvent;

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

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

Additionally, the class needs to be registered as a service and tagged with kernel.event_subscriber:

<?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\PriceComponentSubscriber">
<tag name="kernel.event_subscriber" />
</service>

</services>
</container>

Now, every time a Twig Component is rendered, the PriceComponentSubscriber::onPreRender() method is triggered. That's not exactly what I wanted, but it's a first step towards my goal.

Can we figure out which Twig Component the event will be triggered for? Thankfully, the PreRenderEvent parameter has a getComponent() method that exposes the component to render. By checking its type, we can filter out all components to which we are not interested in subscribing:

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

We can use the getVariables() and setVariables() method calls to access existing variables and set new variables for the component:

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

$variables = $event->getVariables();
$variables['badge'] = 'This is a price badge!'
$event->setVariables($variables);
}

As you can see in the code example, we set the template variable badge to the value This is a price badge!. The next time the component gets rendered, our newly created badge variable will exist in the Twig scope and can be rendered now.

The final piece of the puzzle is the Twig template. We need to copy the original template from Sylius Shop Bundle in our templates directory. In this case, the file I need to create is templates/bundles/SyliusShopBundle/product/common/price.html.twig with the following content:

{% if has_discount %}
<span {{ sylius_test_html_attribute('product-original-price') }}><del>{{ original_price }}</del></span>
{% endif %}
<span {{ sylius_test_html_attribute('product-price') }}>{{ price }}</span>
{% if badge is defined %}
<span {{ sylius_test_html_attribute('product-price-badge') }}>{{ badge }}</span>
{% endif %}

As this example shows, Twig and the Symfony UX components make it easy to customize Sylius core logic in the templates without much copy & paste code. In the long run, this helps with future upgrades.