通过向条件添加自定义 SQL 将某些产品拉到前面

Pull Certain Products to Front by Adding Custom SQL to Criteria

我们的目标是将产品列表中的某些促销产品拉到前面。

重要提示:促销产品因类别/过滤器而异,因此仅插入自定义字段或使用已内置的“促销产品”标志是行不通的. 我们已经可以访问要拉到前面的产品 ID,我们只需要相应地对列表进行排序。

我们订阅了 ProductListingCriteriaEvent::class 并尝试了一些东西 - 基于 - 就像这样:

$criteria = $event->getCriteria()
$sortings = $criteria->getSorting();
$criteria->resetSorting();
 
$criteria->addSorting(new FieldSorting('FIELD(id, 0x123456...)', FieldSorting::DESCENDING));

foreach($sortings as $sorting) {
    $criteria->addSorting($sorting);
}

其中 0x123456... 将是要拉到前面的产品的 UUID。

这当然行不通,因为 Shopware 需要一个字段。

是否可以出于这个原因创建类似“虚拟”字段的东西,或者是否有其他方法可以插入这样的原始 SQL 部分?

原始 SQL 在设计上不受支持,因为如果您使用 ElasticSearch 集成,则具有该条件的查询也可以由 ElasticSearch 解析和执行。

您可以使用您排序的简单自定义字段,而不是在读取中处理所有内容。您可以向产品添加一个 myCustomSorting 自定义字段,并将您希望首先显示的所有产品的该字段的值设置为 1。然后扩展条件以首先按该字段排序。

通过装饰 QueryBuilder 添加新的自定义排序工具

我们可以实现一个新的排序 class,它将特定的 id 拉到前面,然后修饰 CriteriaQueryBuilder 以添加这种新的排序类型。

实现细节(在 Shopware 6.4.6.0 上测试)

首先我们定义一个class来保存新排序方法的信息:

CustomSorting.php

<?php declare(strict_types=1);

namespace ExampleProductListing\Framework\DataAbstractionLayer\Search\Sorting;

use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;


class CustomSorting extends FieldSorting
{
    private array $ids = [];


    public function addId(string $id)
    {
        $this->ids[] = $id;
    }

    public function getIds()
    {
        return $this->ids;
    }
}

接下来,我们为CriteriaQueryBuilder定义一个装饰器:

services.xml

<service id="ExampleProductListing\Framework\DataAbstractionLayer\Dbal\CriteriaQueryBuilderDecorator"
             decorates="Shopware\Core\Framework\DataAbstractionLayer\Dbal\CriteriaQueryBuilder">
    <argument type="service" id="ExampleProductListing\Framework\DataAbstractionLayer\Dbal\CriteriaQueryBuilderDecorator.inner"/>
    <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Search\Parser\SqlQueryParser"/>
    <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper"/>
    <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Search\Term\SearchTermInterpreter"/>
    <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Search\Term\EntityScoreQueryBuilder"/>
    <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Dbal\JoinGroupBuilder"/>
    <argument type="service"
              id="Shopware\Core\Framework\DataAbstractionLayer\Dbal\FieldResolver\CriteriaPartResolver"/>

</service>

接下来,我们实现装饰器,它包含使用 FIELD() 方法生成 SQL 的新逻辑。

CriteriaQueryBuilderDecorator.php

<?php declare(strict_types=1);

namespace ExampleProductListing\Framework\DataAbstractionLayer\Dbal;

use ExampleProductListing\Framework\DataAbstractionLayer\Search\Sorting\CustomSorting;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\CriteriaQueryBuilder;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\FieldResolver\CriteriaPartResolver;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\JoinGroupBuilder;
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\QueryBuilder;
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Parser\SqlQueryParser;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Term\EntityScoreQueryBuilder;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Term\SearchTermInterpreter;

class CriteriaQueryBuilderDecorator extends CriteriaQueryBuilder
{
    private $decoratedService;


    /***
     * @var EntityDefinitionQueryHelper
     */
    private $helper;

    public function __construct(
        CriteriaQueryBuilder        $decoratedService,
        SqlQueryParser              $parser,
        EntityDefinitionQueryHelper $helper,
        SearchTermInterpreter       $interpreter,
        EntityScoreQueryBuilder     $scoreBuilder,
        JoinGroupBuilder            $joinGrouper,
        CriteriaPartResolver        $criteriaPartResolver
    )
    {
        $this->decoratedService = $decoratedService;
        $this->helper = $helper;

        parent::__construct($parser, $helper,$interpreter, $scoreBuilder, $joinGrouper, $criteriaPartResolver);
    }

    public function getDecorated(): CriteriaQueryBuilder
    {
        return $this->decoratedService;
    }

    public function addSortings(EntityDefinition $definition, Criteria $criteria, array $sortings, QueryBuilder $query, Context $context): void
    {
        foreach ($sortings as $sorting) {
            if ($sorting instanceof CustomSorting) {

                $accessor = $this->helper->getFieldAccessor($sorting->getField(), $definition, $definition->getEntityName(), $context);

                $ids = implode(',', array_reverse($sorting->getIds()));
                if (empty($ids)) {
                    continue;
                }

                $query->addOrderBy('FIELD(' . $accessor . ',' . $ids . ')', 'DESC');
            } else {
                $this->decoratedService->addSortings($definition, $criteria, [$sorting], $query, $context);
            }
        }
    }

    public function build(QueryBuilder $query, EntityDefinition $definition, Criteria $criteria, Context $context, array $paths = []): QueryBuilder
    {
        return parent::build($query, $definition, $criteria, $context, $paths);
    }

    public function addFilter(EntityDefinition $definition, ?Filter $filter, QueryBuilder $query, Context $context): void
    {
        parent::addFilter($definition, $filter, $query, $context);
    }

}

如何使用新的排序方式

最后,在构建标准时(例如在 ProductListingCriteriaEvent 中),我们可以通过指定 ID 将特定产品拉到前面。 (此处硬编码,在现实世界中它们来自不同的来源,这取决于所选的过滤器)

    $customSorting = new CustomSorting('product.id');
    $customSorting->addId('0x76f9a07e153645d7bd8ad62abd131234');
    $customSorting->addId('0x76a890cb23ea433a97006e71cdb75678');
    $event->getCriteria()
        ->addSorting($customSorting);

兼容性

这仅适用于 SQL 引擎。如果 ElasticSearch 也应该被支持,这可能也可以通过装饰 ElasticSearch Query Buidler 来实现。