Changing the template loading order

This problem popped up on  Slack a few times and I was confronted with it on a recent project: when the views property in the theme.json file specifies that the theme should be searched for templates first, a plugin cannot override this block anymore.

This behavior really got in the way and so I searched for a way to modify the template loading order within a plugin.

Shopware builds the template loading order with the ThemeInheritanceBuilder class, which conveniently implements the ThemeInheritanceBuilderInterface. It has only one public function and an array as the return value, making this class a perfect fit for decorating, as we can take the returned array of this service and return a modified version of the original value.

The decorator code may look like this:

<?php

namespace KplngiThemeViewLoadingOrder\Service;

use Shopware\Storefront\Theme\Twig\ThemeInheritanceBuilderInterface;

class ThemeInheritanceBuilderDecorator implements ThemeInheritanceBuilderInterface
{
    /**
     * @var ThemeInheritanceBuilderInterface
     */
    private $themeInheritanceBuilder;

    /**
     * @var string
     */
    private $templateName = 'KplngiThemeViewLoadingOrder';

    public function __construct(ThemeInheritanceBuilderInterface $themeInheritanceBuilder)
    {
        $this->themeInheritanceBuilder = $themeInheritanceBuilder;
    }

    public function build(array $bundles, array $themes): array
    {
        $inheritanceStack = $this->themeInheritanceBuilder->build($bundles, $themes);

        if (($key = array_search($this->templateName, $inheritanceStack)) !== false) {
            unset($inheritanceStack[$key]);
            array_unshift($inheritanceStack, $this->templateName);
        }

        return $inheritanceStack;
    }
}

Let’s take a look at the code. The class implements the ThemeInheritanceBuilderInterface, guaranteeing compatibility with the decorated service. Further, two properties are declared: the ThemeInheritanceBuilder and the templateName. The ThemeInheritanceBuilder is the decorated service and to get access to it, we need to initialize our decorator with it. The templateName is set to the name of the example plugin KplngiThemeViewLoadingOrder. You can enter the name of any plugin and its template will be loaded first. In the build function the original return array is assigned to inheritenceStack by calling the decorated classes build function. If templateName exists in the array, it is removed from its current position and then prepended to the inheritanceStack array, making it the first to be loaded. The modified array is returned to the caller, overriding the loading order set by the theme.json.

The decorator is loaded in the services.xml:

<service
    id="KplngiThemeViewLoadingOrder\Service\ThemeInheritanceBuilderDecorator"
    decorates="Shopware\Storefront\Theme\Twig\ThemeInheritanceBuilderInterface"
>
<argument 
    type="service" 
    id="KplngiThemeViewLoadingOrder\Service\ThemeInheritanceBuilderDecorator.inner" 
/>
</service>

Here we define the ID of the decorator class and which class  is to be decorated. The latter is then being passed to the former, which let’s us access the original build function.

And that is it. Now you can set a template at the top of the loading order! Of course, more complex orders are possible – just modify the template array.