You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"
    ]);
}

问题分析

之前的实现存在两个核心问题:

  1. 插件afterPrepareProductCollection方法返回$this而非原方法的$result,打断了Magento的链式调用,导致插件逻辑未被正确执行
  2. 手动拼接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;
    }
}

方案二:添加可选排序选项(类似价格排序)

如果希望让用户手动选择该排序规则,可按以下步骤实现:

  1. 后台配置:进入Stores -> Attributes -> Product,编辑「Promotion」属性,设置Used for Sorting in Product Listing为Yes
  2. 系统配置:进入Stores -> Configuration -> Catalog -> Catalog -> Storefront -> Product Listing Sort by,添加该属性到可选列表
  3. 插件调整:通过插件修改排序逻辑,当用户选择该属性排序时,优先展示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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 04:59:55