Proof of concept — Symfony UX and Shopware 6
As some of you may know, the e-commerce platform Shopware 6 is based on the popular PHP framework Symfony. But first things first!
In June this year, after two years of development, the makers of Symfony finally released Symfony UX, a side project that offers the ability to seamlessly integrate JS into any Symfony application. Besides some useful components for the integration of React or Vue applications as well as examples like an Ajax-based autocomplete search function or lazy image loading, one thing in particular caught my attention:
Twig Components, PHP classes that render themselfes, now finally allow us to create reusable, object-oriented templates by associating Twig template files with PHP classes.
And since this opens up completely new possibilities for programming, it’s time to start a proof of concept!
Let’s stake out the territory
The following PoC will take place in the environment of a bare, i.e. freshly installed, Shopware 6 instance. The aim will be to use a Twig component to easily bring data from the database to any location in the Shopware storefront template.
1. The old fashioned way
To outline the usual approach, I would like to refer to the official Shopware documentation. The article Add data to the storefront shows how to determine the number of all active items and display it in the footer of the storefront.
In order to do this, you first need to subscribe to a specific event. Because in this example we are dealing with a pagelet, whose event we are subscribing to, Shopware recommends going via the Store API instead of directly call the data abstraction layer to fetch data.
To do so, we need to create a kind of controller class with corresponding routes. In this class there is a load method that contains the actual logic to retrieve the data.
Finally, we consume this method in the event subscriber and append the received data to the pagelet as an “extension”.
This is just a brief overview of how it is usually done. Sorry, but I won’t go into the details, you can find them in the official guide mentioned above. However, I would like to add one comment: No matter which way you finally proceed, as a basis for your own code you basically need your own Shopware plugin.
2. The PoC way
Now that we know roughly how the official way to enrich the storefront with additional data goes, let’s deal with a variant that can be made possible thanks to Twig Components.
Prerequisites
To be able to create a Twig component, we first need to install the symfony/ux-twig-components package via Composer.
In order for the delivered package to be loaded in the context of Shopware 6, we have to register the bundle in the bundles.php file — as usual in a Symfony application.
// config/bundles.php$bundles = [
...
Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true]
];
...
Now, instead of subscribing to an event and do all the other this mentioned above, we first create our own PHP class, which looks like the following as an example:
...
class ProductCountComponent
{
public SalesChannelContext $context;
public function __construct(
private EntityRepositoryInterface $productRepository
)
{
}
public function getProductCount(): int
{
$criteria = new Criteria();
$criteria->addFilter(
new EqualsFilter('product.active', true)
);
$criteria->addAggregation(
new CountAggregation('productCount', 'product.id')
);
/** @var CountResult $productCountResult */
$productCountResult = $this->productRepository
->aggregate($criteria, $this->context->getContext())
->get('productCount');
return $productCountResult->getCount();
}
}
...
Next we register this class via the plugin’s services.xml file:
...
<service id="MyPlugin\Twig\Components\ProductCountComponent">
<argument type="service" id="product.repository"/>
<tag name="twig.component" key="product-count" />
</service>
...
It’s important that the service get’s tagged as twig.component to get collected and being treated in a special way. The property key defines the identifier, the component listens to.
That’s it! Really? Yes… almost… Of course, we need to create the Twig component’s template file itself. Because Twig components automatically look in the components folder for an applicable Twig file, all we have to do in our plugin is create a file src/Resources/views/components/product-count.html.twig and define our content to be rendered in it:
<p>
This shop offers you <strong>{{ this.productCount }}</strong> products
</p>
As you can see, the property productCount can be obtained via the public getter of the PHP class.
The grand finale
Instead of showing below how to extend the storefront file for the footer, overwrite the specific template block and render the Twig Component there, I would like to show the following line on its own:
{{ component('product-count', {'context': context}) }}
The installed TwigComponentsBundle provides a Twig function {{ component() }} that can be used ANYWHERE in the storefront. As the first parameter we set the identifier to define which Twig component should be loaded at this point. (The second parameter is optional and offers us in this example the possibility to give the StorefrontContext to our PHP class, since this is necessary for the determination of the number of articles.)
And that’s it! Really? Really!
The big advantage, besides less effort, is mainly that the component can be used independently at any location. In contrast to the official Shopware procedure — There, the data is explicitly bound to the footer pagelet and can therefore only be used in the appropriate template file.
In conclusion, from my point of view, this proof of concept was successful. Just try it yourself, I have made the example available as a Github project: