Zusatzfelder filtern

Hallo Community,

ich würde gerne die Zusatzfelder auf der Kategorieseite als Filter darstellen. Dazu habe ich versucht mich an die Lösung von den Eigenschaften zu orientieren.

Ich habe 2 Lösungen versucht einmal über customFields und einmal über customFieldsSets, beide hängen am Produkt.

Problem:

  1. product.customFields

Die Product Entity speichert die customFields als json und ist somit als Array definiert, dazu kommt noch das diese Einträge dynamische key => values beinhalten. Dies ist schon einmal der erste Unterschied gegenüber z.B. den Eigenschaften (properties). Bei diesen ist alles schön in einzelne Entitys verteilt.

Das heißt ich bekomme es nicht ohne großen Aufwand hin das er mir alles aus dem json encodiert und sich alle Informationen,wie bei den Eigenschaften zieht.

  1. product.customFieldSets

“customFieldsSets” wäre eine Collection, wenn ich diese angebe erhalte ich nur die Id der Collection.

Diese Id könnte ich verwenden und in “custom_field” nachschauen, hier sind aber ebenfalls die Werte als json gespeichert.

 

Derzeitige® Stand/Lösungen:

1. product.customFields

Mein Event Subscriber:

   /**
     * @inheritDoc
     */
    public static function getSubscribedEvents()
    : array
    {
        return [
            ProductListingCollectFilterEvent::class => 'handleFilters',
        ];
    }

    public function handleFilters(ProductListingCollectFilterEvent $event)
    : void {
        $request = $event->getRequest();
        $filters = $event->getFilters() ?? new FilterCollection();
        $filters->add($this->getCustomFieldFilter($request));

    }

    private function getCustomFieldFilter(Request $request)
    {
        $ids = $this->getCustomIds($request);

        if (empty($ids)) {
            return new Filter(
                'custom-fields',
                false,
                [
                    new TermsAggregation('customFields', 'product.customFields'),
                ],
                new MultiFilter(MultiFilter::CONNECTION_OR, []),
                [],
                false
            );
        }

        //TODO: Handle if we have ids
    }

    private function getCustomIds(Request $request)
    : array {
        $ids = $request->query->get('custom-fields', '');
        if ($request->isMethod(Request::METHOD_POST)) {
            $ids = $request->request->get('custom-fields', '');
        }

        if (\is_string($ids)) {
            $ids = explode('|', $ids);
        }

        return array_filter($ids);
    }

Ergebnis im Frontend:

Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\AggregationResultCollection {#5790 ▼
  #elements: array:6 [▼
    "manufacturer" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\EntityResult {#6018 ▶}
    "price" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\StatsResult {#5942 ▶}
    "rating" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\MaxResult {#5903 ▶}
    "shipping-free" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\MaxResult {#5947 ▶}
    "customFields" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Bucket\TermsResult {#6030 ▼
      #buckets: array:2 [▼
        0 => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Bucket\Bucket {#5950 ▼
          #key: "{"custom_easy_car_art": "nw", "custom_easy_car_kilometer": "25000"}"
          #count: 1
          #result: null
          #extensions: []
        }
        1 => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Bucket\Bucket {#5946 ▼
          #key: "{"custom_easy_car_art": "gw", "custom_easy_car_oldtimer": "1", "custom_easy_car_kilometer": "15000"}"
          #count: 1
          #result: null
          #extensions: []
        }
      ]
      -name: "customFields"
      #extensions: []
    }
    "properties" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\EntityResult {#5960 ▶}
  ]
  #extensions: []
}

Hier bekomme ich dann nur json in “key” zurück.

 

2. product.customFields

Mein Event Subscriber:

 'handleFilters',
        ];
    }

    public function handleFilters(ProductListingCollectFilterEvent $event)
    : void {
        $request = $event->getRequest();
        $filters = $event->getFilters() ?? new FilterCollection();
        $filters->add($this->getCustomFieldFilter($request));

    }

    private function getCustomFieldFilter(Request $request)
    {
        $ids = $this->getCustomIds($request);

        if (empty($ids)) {
            return new Filter(
                'custom-fields',
                false,
                [
                    new TermsAggregation('customFields', 'product.customFieldSets.id'),
                ],
                new MultiFilter(MultiFilter::CONNECTION_OR, []),
                [],
                false
            );
        }

        //TODO: Handle if we have ids
    }

    private function getCustomIds(Request $request)
    : array {
        $ids = $request->query->get('custom-fields', '');
        if ($request->isMethod(Request::METHOD_POST)) {
            $ids = $request->request->get('custom-fields', '');
        }

        if (\is_string($ids)) {
            $ids = explode('|', $ids);
        }

        return array_filter($ids);
    }

}

Ergebnis im Frontend:

Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\AggregationResultCollection {#5790 ▼
  #elements: array:6 [▼
    "manufacturer" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\EntityResult {#6018 ▶}
    "price" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\StatsResult {#5942 ▶}
    "rating" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\MaxResult {#5903 ▶}
    "shipping-free" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\MaxResult {#5947 ▶}
    "customFields" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Bucket\TermsResult {#6091 ▼
      #buckets: array:1 [▼
        0 => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Bucket\Bucket {#5946 ▼
          #key: "42c39511bf81414fadc0fb54f53a5738"
          #count: 2
          #result: null
          #extensions: []
        }
      ]
      -name: "customFields"
      #extensions: []
    }
    "properties" => Shopware\Core\Framework\DataAbstractionLayer\Search\AggregationResult\Metric\EntityResult {#5960 ▶}
  ]
  #extensions: []
}

Hier bekomme ich dann die ID in key zurück.

 

Fragen:

  1. Sind die Zusatzfelder derzeit überhaupt dafür vorgesehen?

  2. Wie bekomme ich es hin, dass er alles automatisiert, wie bei den Properties auflöst ? Wahrscheinlich nicht so einfach, oder?

  3. Ist der Weg über Entity Extension besser? 

 

Vielen Dank für die Bemühungen und Hilfe!

 

Viele Grüße

Tobias

 

Also generell sind die CustomFields nur für einfache Daten gedacht, die am Produkt hängen und ggf. in der Storefront ausgegeben werden sollen. Um sowas wie Filter damit zu realisieren, halte ich die CustomFields für die falsche Lösung. Alleine schon, weil du die JSON-Felder nicht so performant als Filterkriterium umsetzen kannst. Sicherlich kann man das sich irgendwie zusammen hacken, würde aber sagen, dafür sind die einfach nicht gedacht.

1 „Gefällt mir“
  1. Wie bekomme ich es hin, dass er alles automatisiert, wie bei den Properties auflöst ? Wahrscheinlich nicht so einfach, oder?

So viel Code im Subscriber:

class StorefrontSubscriber implements EventSubscriberInterface
{
    /*
     * @var MerchantService
     */
    private $merchantService;

    public function __construct(
        MerchantService $merchantService
    )
    {
        $this->merchantService = $merchantService;
    }

    public static function getSubscribedEvents(): array
    {
        return [
            ProductListingCollectFilterEvent::class => 'addFilter',
            ProductListingCriteriaEvent::class => 'addCriteria'
        ];
    }

    public function addCriteria(ProductListingCriteriaEvent $event)
    {
        $criteria = $event->getCriteria();
        //$criteria->addAssociation('merchantStocks.merchant');
        //$criteria->addAggregation(new EntityAggregation('merchants', 'product.merchantStocks.merchant.id', MerchantDefinition::ENTITY_NAME));
        $criteria->addAssociation('merchants');
        $criteria->addAggregation(new EntityAggregation('merchants', 'product.merchants.id', MerchantDefinition::ENTITY_NAME));
    }

    public function addFilter(ProductListingCollectFilterEvent $event)
    {
        $filters = $event->getFilters();
        $request = $event->getRequest();

        $ids = array_filter(explode('|', $request->query->get('merchant', '')));

        $filter = new Filter(
            'merchants',
            !empty($ids),
            [new EntityAggregation('merchants', 'product.merchants.id', MerchantDefinition::ENTITY_NAME)],
            new EqualsAnyFilter('product.merchantStocks.productId', $ids),
            $ids
        );

        $filters->add($filter);
    }
}

 

So viel Code im Twig Template:

{% sw_extends '@Storefront/storefront/component/listing/filter-panel.html.twig' %}

{% block component_filter_panel_item_manufacturer %}
    {% set merchants = listing.aggregations.get('merchants') %}
    {% if not merchants.entities is empty %}
        {% set merchantsSorted = merchants.entities|sort((a, b) => a.translated.name|lower > b.translated.name|lower) %}

        {% sw_include '@Storefront/storefront/component/listing/filter/filter-multi-select.html.twig' with {
            elements: merchantsSorted,
            sidebar: sidebar,
            name: 'merchant',
            displayName: 'Merchant'
        } %}
    {% endif %}

    {{ parent() }}
{% endblock %}

 

 

  1. Ist der Weg über Entity Extension besser? 

Ja Deine CustomEntity + die ProductExtension, hab hier eine bsp Extension für zwei unterschiedliche Assoziationen - ist aber anfangs ein bisschen kniffelig

class ProductExtension extends EntityExtension
{
    public function getDefinitionClass(): string
    {
        return ProductDefinition::class;
    }

    public function extendFields(FieldCollection $collection): void
    {
        $collection->add(
            new OneToManyAssociationField(
                'merchantStocks',
                MerchantStockDefinition::class,
                'product_id'
            )
        );

        $collection->add(
            new ManyToManyAssociationField(
                'merchants',
                MerchantDefinition::class,
                MerchantStockDefinition::class,
                'product_id',
                'moorl_merchant_id'
            )
        );
    }
}

 

2 „Gefällt mir“

Danke. Mit diesem Input bin ich nun zum Ziel gekommen! Thumb-Up