How to add basic page data to a custom page in Shopware 6
In the Shopware 6 plugin tutorial, I have described a way to use our own custom storefront controller to create a custom page with custom content. Today, I would like to expand this topic a bit by adding the standard Shopware 6 page data to our custom page. That way, we will be able to for example display the standard navigation, but also access some useful system values.
A recap on how to render a custom page in Shopware 6
So first, let us do a small recap to see, how to create a custom controller. These are the four components, that were used in the plugin tutorial. All paths are relative to the plugin directory, for example custom/plugins/SkeletonPlugin. If you have troubles putting it all together, you can download the sample plugin from the Shopware 6 plugin tutorial article. Just copy it to your custom/plugins directory and then install and activate it in the backend or using the console commands.
The controller – src/Storefront/Controller/SkeletonController.php:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<?php namespace SkeletonPlugin\Storefront\Controller; use Shopware\Core\Framework\Routing\Annotation\RouteScope; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Controller\StorefrontController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; /** * @RouteScope(scopes={"storefront"}) */ class SkeletonController extends StorefrontController { /** * @Route("/skeleton", name="frontend.skeletonplugin.skeleton", methods={"GET"}) */ public function showPage(Request $request, SalesChannelContext $context): Response { return $this->renderStorefront('@SkeletonPlugin/storefront/page/skeleton/index.html.twig', [ 'customParameter' => 'Custom parameter value' ]); } } |
The routing setup – src/Resources/config/routes.xml:
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing https://symfony.com/schema/routing/routing-1.0.xsd"> <import resource="../../Storefront/Controller/**/*Controller.php" type="annotation" /> </routes> |
The service registration – src/Resources/config/services.xml:
1 2 3 4 5 |
<service id="SkeletonPlugin\Storefront\Controller\SkeletonController" public="true"> <call method="setContainer"> <argument type="service" id="service_container"/> </call> </service> |
The template – src/Resources/views/storefront/page/skeleton/index.html.twig:
1 2 3 4 5 6 |
{% sw_extends '@Storefront/storefront/base.html.twig' %} {% block base_content %} <h1>Skeleton controller works!</h1> {{ dump() }} {% endblock %} |
This screenshot from Shopware 6 store with default template and demo data shows, that it works:
The Twig template is loaded and shows the variables, that are available to it. However, there si one problem. The menu is missing the categories, that are present on the other pages, like for example on the store’s homepage:
And the currency switch is missing as well! That is not good. On most of the store’s pages, we want these parts of the user interface to be present to maintain consistency. We definitely need to do something about it!
The analysis of missing page components
When something is missing on one page, that is present on another, we can use the browser developer tools to take a closer look at that particular place in the source code. In this case, we just have to right click on the menu and inspect the element. There we can see, that the menu has CSS class ‘nav main-navigation-menu’. Searching for this in the code will luckily yield just one result, so we can be sure, that we are in the right place:
This right place is the template file Resources/views/storefront/layout/navigation/navigation.html.twig. The file navigation.html.twig is the Shopware 6 standard template for the navigation, that you usually want to override in your custom theme. But we are working with the demo store now and we have no other theme at our disposal. In any case, we will not edit the core files, because we do not want the next update to overwrite it, among other reasons. So editing and overriding is not an option. But we do not have to.
If you investigate a bit, you will find out, that within the template, there is a place, where the categories are being displayed. Well, would be, if only they were available to the Twig template. To be more specific, we are missing the variable page.header.navigation.tree in the Twig block layout_main_navigation_menu_items.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
{% block layout_main_navigation_menu_items %} {% if page.product is defined %} {% set activePath = page.product.categoryTree %} {% else %} {% set activePath = page.header.navigation.active.path %} {% endif %} {% for treeItem in page.header.navigation.tree %} {% set category = treeItem.category %} {% set name = category.translated.name %} {# @deprecated tag:v6.5.0 - Use "category.id" directly instead. #} {% set categorId = category.id %} {% block layout_main_navigation_menu_item %} {% if category.type == 'folder' %} <div class="nav-link main-navigation-link" {% if treeItem.children|length > 0 %} data-flyout-menu-trigger="{{ category.id }}" {% endif %} title="{{ name }}"> <div class="main-navigation-link-text"> <span itemprop="name">{{ name }}</span> </div> </div> {% else %} <a class="nav-link main-navigation-link{% if category.id == page.header.navigation.active.id or category.id in activePath %} active{% endif %}" href="{{ category_url(category) }}" itemprop="url" {% if treeItem.children|length > 0 %}data-flyout-menu-trigger="{{ category.id }}"{% endif %} {% if category_linknewtab(category) %}target="_blank"{% endif %} title="{{ name }}"> <div class="main-navigation-link-text"> <span itemprop="name">{{ name }}</span> </div> </a> {% endif %} {% endblock %} {% endfor %} {% endblock %} |
The solution by using a Shopware 6 core class
So how can we get the page variables and make them available to the Twig templates? The answer is: GenericPageLoader. This core Shopware class contains a method named ‘load’. This method is able to get all the common information, that is available on every page. This includes the header, the footer and other stuff, based on the current context, like shipping and payment methods.
GenericPageLoader is a class like any other. That means, that we can inject it to the storefront controller. There we add the ‘load’ method call and save its result to the variable ‘page’, just before we call the ‘renderStorefront’ method. Then we just add ‘page’ as a parameter to it to make it available for the Twig template. The code looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
<?php namespace SkeletonPlugin\Storefront\Controller; use Shopware\Core\Framework\Routing\Annotation\RouteScope; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Storefront\Controller\StorefrontController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; use Shopware\Storefront\Page\GenericPageLoader; /** * @RouteScope(scopes={"storefront"}) */ class SkeletonController extends StorefrontController { protected $genericPageLoader; public function __construct( GenericPageLoader $genericPageLoader ) { $this->genericPageLoader = $genericPageLoader; } /** * @Route("/skeleton", name="frontend.skeletonplugin.skeleton", methods={"GET"}) */ public function showPage(Request $request, SalesChannelContext $context): Response { $page = $this->genericPageLoader->load($request, $context); return $this->renderStorefront('@SkeletonPlugin/storefront/page/skeleton/index.html.twig', [ 'page' => $page ]); } } |
Additionally, do not forget to update the services.xml, so that the dependency injection works, as it should:
1 2 3 4 5 6 |
<service id="SkeletonPlugin\Storefront\Controller\SkeletonController" public="true"> <argument type="service" id="Shopware\Storefront\Page\GenericPageLoader"/> <call method="setContainer"> <argument type="service" id="service_container"/> </call> </service> |
Now we can reload our custom page and see the results:
As you can see, the menu is there and the currency switch as well. The dump from the Twig template shows the newly available variable ‘page’ with all the yummy stuff, that helps render our Shopware store’s page. So this is how we can add basic page data to any custom template in Shopware 6 to be able to display the standard components of the page.
This article was inspired by one of your questions, that I have received in a comment under another article on my Shopware 6 blog. I hope it will help not just the guy, who made me aware of the missing components on the page, but other people as well. So feel free to add your questions or insights to the comments below and who knows, another useful article might come of it eventually.. 😉