Shopware 6前台加载非接入点分类菜单树的正确方法
解决Shopware 6加载非销售渠道接入点分类树的问题
问题背景
需在Shopware 6前台头部添加两级深度的下拉菜单,展示页脚三类菜单中的两类。由于页脚服务导航为硬编码一级导航且已占用,单独创建了顶级分类树(子分类为页脚导航的内部链接),但当前代码仅能加载销售渠道接入点下的分类,选择其他分类会触发CategoryNotFoundException——根源是/src/Core/Content/Category/SalesChannel/NavigationRoute.php的validate()函数限制了仅能加载接入点关联分类。
解决方案
方案1:直接通过CategoryRepository构建分类树
绕过NavigationLoader的接入点限制,直接使用仓储查询分类并手动构建树结构。
1. 修改服务注入配置
更新主题的services.xml,将NavigationLoader替换为CategoryRepository:
<services> <service id="MyExample\Subscriber\HeaderPageletLoadedSubscriber"> <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/> <argument type="service" id="Shopware\Core\Content\Category\CategoryRepository"/> <tag name="kernel.event_subscriber"/> </service> </services>
2. 重写订阅者代码
<?php declare(strict_types=1); namespace MyExample\Subscriber; use Shopware\Core\Content\Category\CategoryEntity; use Shopware\Core\Content\Category\CategoryRepositoryInterface; use Shopware\Core\Content\Category\Tree\Tree; use Shopware\Core\Content\Category\Tree\TreeItem; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting; use Shopware\Core\System\SalesChannel\SalesChannelContext; use Shopware\Core\System\SystemConfig\SystemConfigService; use Shopware\Storefront\Pagelet\Header\HeaderPageletLoadedEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class HeaderPageletLoadedSubscriber implements EventSubscriberInterface { private CategoryRepositoryInterface $categoryRepository; private SystemConfigService $systemConfigService; public function __construct( SystemConfigService $systemConfigService, CategoryRepositoryInterface $categoryRepository ) { $this->systemConfigService = $systemConfigService; $this->categoryRepository = $categoryRepository; } public static function getSubscribedEvents(): array { return [ HeaderPageletLoadedEvent::class => 'onHeaderPageletLoaded' ]; } public function onHeaderPageletLoaded(HeaderPageletLoadedEvent $event) { $context = $event->getSalesChannelContext(); $salesChannelId = $context->getSalesChannelId(); $rootCategoryId = $this->systemConfigService->get('MyExample.config.serviceRoot', $salesChannelId); if (!$rootCategoryId) { return; } // 加载根分类 $rootCriteria = new Criteria([$rootCategoryId]); $rootCategory = $this->categoryRepository->search($rootCriteria, $context->getContext())->first(); if (!$rootCategory) { return; } // 加载一级子分类及二级子分类 $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('parentId', $rootCategoryId)); $criteria->addFilter(new EqualsFilter('active', true)); $criteria->addSorting(new FieldSorting('position')); $criteria->addAssociation('children'); $criteria->getAssociation('children') ->addFilter(new EqualsFilter('active', true)) ->addSorting(new FieldSorting('position')); $childCategories = $this->categoryRepository->search($criteria, $context->getContext())->getEntities(); // 构建Tree结构 $treeItems = []; foreach ($childCategories as $child) { $childTreeItem = $this->buildTreeItem($child); // 添加二级子分类 $grandchildren = $child->getChildren(); if ($grandchildren) { $childTreeItem->setChildren(array_map(fn(CategoryEntity $cat) => $this->buildTreeItem($cat), $grandchildren->getElements())); } $treeItems[] = $childTreeItem; } $tree = new Tree(); $tree->setTree($treeItems); $tree->setRoot($rootCategoryId); $event->getPagelet()->addExtension('MyMenu', $tree); } private function buildTreeItem(CategoryEntity $category): TreeItem { $item = new TreeItem(); $item->setId($category->getId()); $item->setName($category->getName()); $item->setUrl($category->getSeoUrl() ?? $category->getInternalLink()); $item->setActive(false); $item->setChildren([]); return $item; } }
方案2:自定义NavigationLoader绕过验证
基于核心NavigationLoader扩展,跳过接入点验证逻辑。
1. 创建自定义加载器类
<?php declare(strict_types=1); namespace MyExample\Service; use Shopware\Core\Content\Category\Service\NavigationLoader; use Shopware\Core\Content\Category\Tree\Tree; use Shopware\Core\System\SalesChannel\SalesChannelContext; class UnrestrictedNavigationLoader extends NavigationLoader { public function load(string $navigationId, SalesChannelContext $context, ?string $rootId = null, int $depth = 2): Tree { // 直接调用核心树构建逻辑,跳过接入点验证 return $this->buildTree( $this->loadCategories($navigationId, $context, $depth), $navigationId, $rootId ?? $navigationId, $context ); } }
2. 配置服务注入
更新services.xml,注入自定义加载器:
<services> <service id="MyExample\Service\UnrestrictedNavigationLoader"> <argument type="service" id="Shopware\Core\Content\Category\CategoryRepository"/> <argument type="service" id="Shopware\Core\Content\Category\Tree\TreeBuilder"/> <argument type="service" id="Shopware\Core\Content\Seo\SeoUrlPlaceholderHandler"/> <argument type="service" id="Shopware\Core\System\SalesChannel\SalesChannelRepository"/> </service> <service id="MyExample\Subscriber\HeaderPageletLoadedSubscriber"> <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService"/> <argument type="service" id="MyExample\Service\UnrestrictedNavigationLoader"/> <tag name="kernel.event_subscriber"/> </service> </services>
原订阅者代码无需修改,直接使用自定义加载器即可加载任意分类树。
注意事项
- 确保加载的分类处于
active状态,避免展示未发布内容 - 若需SEO友好URL,需确保分类的SEO配置完整
- 自定义加载器需保持与Shopware核心逻辑对齐,降低版本升级兼容风险
内容的提问来源于stack exchange,提问作者Jürgen Hörmann
相关产品推荐
相关产品推荐

