如何在Shopware中扩展搜索结果以包含外部API查询的实体
解决方案:Shopware搜索结果添加外部产品
一、修复搜索结果计数与排序问题
你之前只往ProductSearchResult的items里添加外部产品,但没同步更新total属性——这个属性是独立维护的,不会自动根据items数量计算,所以才会出现计数和实际条目不符的情况。同时要确保外部产品始终排在内部结果之后,具体实现步骤如下:
事件订阅者代码实现
创建事件订阅类,在ProductSearchResultEvent中合并内部、外部结果并更新计数:
<?php namespace YourPlugin\Subscriber; use Shopware\Core\Content\Product\Events\ProductSearchResultEvent; use Shopware\Core\Content\Product\ProductEntity; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class ExternalProductSearchSubscriber implements EventSubscriberInterface { public static function getSubscribedEvents(): array { return [ ProductSearchResultEvent::class => 'onProductSearchResult', ]; } public function onProductSearchResult(ProductSearchResultEvent $event): void { $searchResult = $event->getResult(); $internalItems = $searchResult->getItems(); $internalTotal = $searchResult->getTotal(); // 调用外部API获取匹配的产品 $externalProducts = $this->fetchExternalProducts($event->getSearchTerm()); // 将外部产品转换为Shopware兼容的ProductEntity(保证前端渲染字段一致) $externalItems = $this->mapToProductEntities($externalProducts); // 合并结果:内部在前,外部在后 $mergedItems = array_merge($internalItems, $externalItems); $newTotal = $internalTotal + count($externalItems); // 更新搜索结果的条目和计数 $searchResult->setItems($mergedItems); $searchResult->setTotal($newTotal); } private function fetchExternalProducts(string $searchTerm): array { // 替换为实际外部API调用逻辑 return [ ['id' => 'ext-1001', 'name' => '外部产品A', 'price' => 89.99, 'thumbnail' => 'https://example.com/ext-a.jpg'], ['id' => 'ext-1002', 'name' => '外部产品B', 'price' => 129.99, 'thumbnail' => 'https://example.com/ext-b.jpg'], ]; } private function mapToProductEntities(array $externalProducts): array { $entities = []; // 用固定货币ID(示例为欧元,根据你的Shopware配置调整) $defaultCurrencyId = 'b7d2554b0ce847cd82f3ac9bd1c0dfca'; foreach ($externalProducts as $product) { $entity = new ProductEntity(); $entity->setId($product['id']); // 用自定义前缀(如ext-)避免和内部产品ID冲突 $entity->setName($product['name']); $entity->setPrice([ [ 'gross' => $product['price'], 'net' => $product['price'] * 0.8, 'currencyId' => $defaultCurrencyId ] ]); $entity->setCover([ 'media' => ['url' => $product['thumbnail']] ]); // 补充其他前端渲染需要的字段(如description、stock等) $entities[] = $entity; } return $entities; } }
二、外部产品详情页处理
因为外部产品未存入Shopware数据库,默认的产品详情路由无法识别,需要通过自定义路由+控制器来实现:
1. 添加自定义路由
在插件的src/Resources/config/routes.xml中定义外部产品详情路由:
<?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"> <route id="frontend.external_product.detail" path="/external-product/{externalId}"> <default key="_controller">YourPlugin\Controller\ExternalProductController::detail</default> </route> </routes>
2. 实现详情页控制器
创建控制器获取外部产品详情并渲染页面:
<?php namespace YourPlugin\Controller; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class ExternalProductController extends AbstractController { #[Route('/external-product/{externalId}', name: 'frontend.external_product.detail', methods: ['GET'])] public function detail(string $externalId): Response { // 调用外部API获取产品详情 $product = $this->fetchExternalProductDetail($externalId); // 复用Shopware默认产品详情模板,或自定义模板 return $this->render('@YourPlugin/storefront/page/product/detail.html.twig', [ 'product' => $product, 'isExternal' => true ]); } private function fetchExternalProductDetail(string $externalId): array { // 替换为实际外部API详情查询逻辑 return [ 'id' => $externalId, 'name' => '外部产品A', 'description' => '这是来自外部API的产品详细描述', 'price' => 89.99, 'images' => ['https://example.com/ext-a-detail.jpg'], 'specs' => ['材质' => '合金', '尺寸' => '30x20cm'] ]; } }
3. 修改前端链接指向
在Shopware的产品卡片模板(如storefront/component/product/card/box-standard.html.twig)中,判断产品ID前缀,将外部产品的链接指向自定义路由:
{% set productUrl = product.id starts with 'ext-' ? path('frontend.external_product.detail', {externalId: product.id}) : seoUrl('frontend.detail.page', {productId: product.id}) %} <a href="{{ productUrl }}" class="product-box-link" title="{{ product.name }}"> <!-- 原有卡片内容 --> </a>
内容的提问来源于stack exchange,提问作者Dirk
相关产品推荐
相关产品推荐

