Magento 2.4.6:按自定义属性特定选项排序商品集合
Magento 2.4.6 实现按自定义属性「Sale」选项全集合排序需求
问题描述
我在Magento 2.4.6中创建了名为「Promotion」的自定义属性(ID:489,代码:special_attributes),包含Promo、Sale等四个选项。需要实现按「Sale」选项对商品目录集合排序,效果类似按价格排序:当前分类下15个标记为Sale的商品,第一页显示12个,剩余3个排在下一页顶部。
此前尝试的代码仅能对当前分页页内商品排序,无法实现全集合排序;编写的Layer类插件未生效,求正确的实现位置与方案。
此前尝试的代码
/** * @param \Magento\Catalog\Model\Layer $subject * @param \Magento\Catalog\Model\Layer $result * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */ public function afterPrepareProductCollection($subject, $result, $collection) { if ($this->request->getFullActionName() !== 'catalog_category_view') { return; } $collection->getSelect()->reset(\Zend_Db_Select::ORDER); $this->joinAttribute('special_atributes', $collection); $this->sortCollectionByPromotionalAction($collection); return $this; } private function joinAttribute($attributeCode, $collection) { $fromPart = $collection->getSelect()->getPart(\Zend_Db_Select::FROM); if (array_key_exists($attributeCode, $fromPart)) { return false; } try { $attribute = $this->attrRepository->get(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, $attributeCode); if (!$attribute) { return false; } $collection->getSelect()->joinLeft( [$attributeCode => $attribute->getBackendTable()], "e.entity_id = {$attributeCode}.entity_id AND {$attributeCode}.attribute_id = {$attribute->getAttributeId()}", [] ); return true; } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { return false; } } /** * @param \Magento\Framework\Data\Collection $collection * @return void */ private function sortCollectionByPromotionalAction($collection) { $alias = 'is_actual_promotional_order'; $expression = new \Zend_Db_Expr( "CASE WHEN FIND_IN_SET('5008', special_atributes.value) THEN 1 ELSE 0 END" ); $collection->getSelect()->columns([ $alias => $expression ]); $collection->getSelect()->order([ "$alias DESC", "e.created_at DESC" ]); }
问题分析
之前的实现存在两个核心问题:
- 插件
afterPrepareProductCollection方法返回$this而非原方法的$result,打断了Magento的链式调用,导致插件逻辑未被正确执行 - 手动拼接SQL语句关联属性表的方式,没有利用Magento原生集合方法处理属性的商店过滤、状态校验等逻辑,容易导致排序仅作用于分页后的子集
正确实现方案
方案一:修复Layer插件(强制全局排序)
修正返回值问题,同时使用Magento原生方法处理属性,确保排序作用于全集合:
<?php namespace Vendor\Module\Plugin; use Magento\Catalog\Model\Layer; use Magento\Catalog\Model\ResourceModel\Product\Collection; use Magento\Catalog\Api\ProductAttributeRepositoryInterface; use Magento\Framework\App\RequestInterface; class LayerPlugin { /** * @var RequestInterface */ private $request; /** * @var ProductAttributeRepositoryInterface */ private $attrRepository; public function __construct( RequestInterface $request, ProductAttributeRepositoryInterface $attrRepository ) { $this->request = $request; $this->attrRepository = $attrRepository; } /** * @param Layer $subject * @param Layer $result * @param Collection $collection * @return Layer */ public function afterPrepareProductCollection(Layer $subject, Layer $result, Collection $collection) { if ($this->request->getFullActionName() !== 'catalog_category_view') { return $result; // 必须返回$result,保证链式调用不中断 } try { // 获取自定义属性 $attribute = $this->attrRepository->get( \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE, 'special_attributes' ); if (!$attribute) { return $result; } // 用原生方法添加属性到集合,自动处理商店、状态过滤 $collection->addAttributeToSelect($attribute->getAttributeCode()); // 重置原有排序规则 $collection->getSelect()->reset(\Zend_Db_Select::ORDER); // 添加优先级排序字段:标记Sale的商品排最前 $priorityAlias = 'is_sale_priority'; $collection->getSelect()->columns([ $priorityAlias => new \Zend_Db_Expr( "CASE WHEN FIND_IN_SET('5008', `{$attribute->getAttributeCode()}`) THEN 1 ELSE 0 END" ) ]); // 应用排序规则:先按Sale优先级倒序,再按创建时间倒序 $collection->getSelect()->order([ "{$priorityAlias} DESC", "e.created_at DESC" ]); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { // 捕获异常,不影响正常流程 } return $result; } }
方案二:添加可选排序选项(类似价格排序)
如果希望让用户手动选择该排序规则,可按以下步骤实现:
- 后台配置:进入
Stores -> Attributes -> Product,编辑「Promotion」属性,设置Used for Sorting in Product Listing为Yes - 系统配置:进入
Stores -> Configuration -> Catalog -> Catalog -> Storefront -> Product Listing Sort by,添加该属性到可选列表 - 插件调整:通过插件修改排序逻辑,当用户选择该属性排序时,优先展示Sale选项的商品:
<?php namespace Vendor\Module\Plugin; use Magento\Framework\App\RequestInterface; use Magento\Catalog\Model\ResourceModel\Product\Collection; class ProductCollectionSortPlugin { /** * @var RequestInterface */ private $request; public function __construct(RequestInterface $request) { $this->request = $request; } /** * @param Collection $subject * @param Collection $result * @return Collection */ public function afterAddOrder(Collection $subject, $result) { $currentSort = $this->request->getParam('order'); // 判断当前是否选择了自定义属性排序 if ($currentSort !== 'special_attributes') { return $result; } // 重置原有排序,添加Sale优先级排序 $subject->getSelect()->reset(\Zend_Db_Select::ORDER); $priorityAlias = 'is_sale_priority'; $subject->getSelect()->columns([ $priorityAlias => new \Zend_Db_Expr( "CASE WHEN FIND_IN_SET('5008', `special_attributes`) THEN 1 ELSE 0 END" ) ]); // 排序规则:Sale优先,再按属性值、创建时间排序 $subject->getSelect()->order([ "{$priorityAlias} DESC", "special_attributes ASC", "e.created_at DESC" ]); return $result; } }
关键注意事项
- 返回值必须正确:插件的
after方法必须返回原方法的$result,否则会中断Magento的链式调用,导致插件失效 - 使用原生集合方法:避免手动拼接SQL关联表,
addAttributeToSelect会自动处理商店、商品状态等过滤条件,确保数据准确性 - 排序时机:排序逻辑必须在集合加载前设置,Magento的分页是基于排序后的全集合计算的,这样才能保证Sale商品按顺序分布在各分页中
- 清理缓存:修改代码后,必须清理
var/cache、generated目录,刷新页面缓存,确保插件生效
内容的提问来源于stack exchange,提问作者Igor Pilat
相关产品推荐
相关产品推荐

