Shopware 6应用:Twig脚本引发内存溢出问题排查
Shopware 6 产品列表徽章脚本内存溢出问题排查与优化
我们编写的Shopware 6产品列表徽章脚本,在产品数量较多时会触发PHP内存不足导致系统崩溃,日志明确提示内存耗尽。虽然已经做了批量获取媒体资源的优化,但代码仍存在几个关键性能问题,导致内存占用过高:
现有代码的核心问题
- 重复遍历与计算冗余:代码中两次完整遍历所有产品,每次遍历还要重复处理产品的分类树、生成徽章列表并排序,大量重复操作会急剧消耗CPU和内存。
- 频繁数组合并的内存损耗:Twig的
merge操作会创建新数组,像逐个添加产品到products数组、每次生成badgeList时的合并,都会产生大量临时数组,导致内存碎片化,累积占用远超预期的内存。 - 分类数据重复处理:每个产品的分类树在收集媒体ID和绑定徽章两个阶段被重复遍历,没有复用之前的计算结果。
优化后的代码
{# @var services \Shopware\Core\Framework\Script\ServiceStubs #} {% set page = hook.page %} {# @var page \Shopware\Storefront\Page\Product\ProductPage #} {% set products = [] %} {% if hook.page.cmsPage.type === 'product_list' %} {% foreach hook.page.cmsPage.sections as section %} {% foreach section.blocks as sectionBlock %} {% if sectionBlock.type !== 'product-listing' %} {% continue %} {% endif %} {% foreach sectionBlock.slots as slot %} {% if slot.type !== 'product-listing' %} {% continue %} {% endif %} {# 直接获取完整产品集合,避免逐个merge数组 #} {% set products = slot.data.listing.entities %} {% endforeach %} {% endforeach %} {% endforeach %} {% endif %} {% if products is not empty %} {# 收集所有分类ID并去重,减少分类查询量 #} {% set categoryIds = products|reduce((carry, product) => carry|merge(product.categoryTree), [])|unique %} {% set categories = services.repository.search('category', {'ids': categoryIds}) %} {# 预生成分类ID到徽章信息的映射表,避免重复处理分类 #} {% set categoryBadgeMap = {} %} {% foreach categories.entities as category %} {% if category.customFields.minds_cd_show_badge %} {% set categoryBadgeMap = categoryBadgeMap|merge({ category.id: { 'priority': category.customFields.minds_cd_badge_priority, 'mediaId': category.customFields.minds_cd_badge } }) %} {% endif %} {% endforeach %} {# 一次性处理所有产品的徽章逻辑,收集媒体ID并建立产品-媒体ID映射 #} {% set productBadgeMap = {} %} {% set mediaIds = [] %} {% foreach products as product %} {% set badgeList = [] %} {% foreach product.categoryTree as categoryId %} {% if categoryBadgeMap[categoryId] is defined %} {% set badgeList = badgeList|merge([categoryBadgeMap[categoryId]]) %} {% endif %} {% endforeach %} {% if badgeList is not empty %} {% set badge = (badgeList|sort((a, b) => a.priority <=> b.priority))|last %} {% set productBadgeMap = productBadgeMap|merge({product.id: badge.mediaId}) %} {% set mediaIds = mediaIds|merge([badge.mediaId]) %} {% endif %} {% endforeach %} {# 媒体ID去重后批量查询,减少内存占用 #} {% set mediaCollection = services.repository.search('media', {'ids': mediaIds|unique}) %} {# 根据预先生成的映射绑定徽章到产品 #} {% foreach products as product %} {% if productBadgeMap[product.id] is defined %} {% set badgeMedia = mediaCollection.get(productBadgeMap[product.id]) %} {% do product.addArrayExtension('mcb', { 'badge': badgeMedia }) %} {% endif %} {% endforeach %} {% endif %}
关键优化点
- 减少数组合并操作:直接获取产品集合而非逐个merge,分类ID先去重再查询,避免不必要的内存开销。
- 预生成分类徽章映射:提前把带徽章的分类信息存入映射表,每次处理产品时直接查表,省去重复检查分类自定义字段的操作。
- 复用计算结果:一次性生成产品与媒体ID的映射关系,避免两次遍历产品时重复生成徽章列表和排序。
- 媒体ID去重:收集媒体ID时先去重,减少媒体查询的数量,降低内存占用。
内容的提问来源于stack exchange,提问作者Leon Schwanitz
相关产品推荐
相关产品推荐

