Show FeatureSets in ProductListing

Hi, ich würde genre mein erstes Shopware Plugin mit Euch teilen. Die Idee war die Essentialcharacteristics (basierend auf product_features) im Produkt Listing anzuzeigen. Der folgedende Code funktioniert, aber ich denke man kann das in Shopware wesentlich eleganter lösen.

Also, ich habe einen Subscriber implementiert, der aucf das ProductListingResultEvent reagiert. Der zugehörige Handler ist wie folgt implementiert:

        $extensionData = array();

        $result = $event->getResult();
        $products = $result->getElements();
        foreach($products as $product){
            $productId = $product->getId();

            $criteria = new Criteria([$productId]);
            $criteria->addAssociation('featureSet');
            $criteria->addAssociation('properties');
            $productEntity = $this->productRepository->search($criteria, $event->getContext());
            $productEntity = $productEntity->getEntities()->first();

            $productId = $productEntity->getId();
            $productProperties = $productEntity->getProperties();
            $extensionData[$productId] = array ();
            
            $featureSet = $productEntity->getFeatureSet();
            if ($featureSet != null){
                $features = $featureSet->getFeatures();
                foreach ($features as $feature){
                    $featureId = $feature['id'];
                    $featureName = $feature['name'];
                    
                    foreach ($productProperties as $property ){
                        $propertyId = $property->getGroupId();
                        $propertyName = $property->getname();
                       
                        if ($featureId == $propertyId){
                            array_push($extensionData[$productId],array(
                                "featureName" => $featureName,
                                "featureValue" => $propertyName,
                            ));
                        }
                    }    
                }   
            }
            
        }
        $event->getResult()->addExtension('product_features', new ArrayStruct($extensionData));

Im Frontend habe ich die View box-standard.html.twig wie folgt überschrieben:

{% sw_extends '@Storefront/storefront/component/product/card/box-standard.html.twig' %}

{% block component_product_box_description %}
    {{ parent() }}
    <p>
    {% set product_features = searchResult.extensions.product_features %}
    <ul style="list-style: none;">
        {% for product_feature in product_features[id] %}
    <li>{{ product_feature.featureName}} : <b>{{product_feature.featureValue }}</b></li>
        {% endfor %}
    </ul>
  
    </p>
   {% endblock %}

Ich würde mich über Euer Feedback, Kritik und ggf. Anregungen für eine bessere, elegenatere Lösung sehr freuen.
Vielen Dank

Markus

Das sieht ziemlich zielführend aus. Programmiere selbst zwar noch keine Ewigkeiten mit Shopware, aber eine viel einfachere Lösung gibt es glaube ich nicht.

1 „Gefällt mir“

Vielen Dank. Das freut mich natürlich sehr.

Was mir halt nur so wenig an meiner Lösung gefällt, dass ich eine separate Extension anhängen musste, die wiederum alle Produkte mit den entsprechenden Feature enthält. Ich fände es irgendwie besser, die Information direkt an der entsprechenden Stelle im Resultobject zu ergänzen, und nicht als separate Extension…

Ich würds trotzdem mal lieber mit dem ProductListingCriteriaEvent probieren.

1 „Gefällt mir“

Hey Moorleiche, interessant. Das hatte ich zwischendurch auch überlegt. Doch, wo liegen die Vorteile?

Im konkreten Fall kann ich dazu nichts sagen, generell halte ich die Idee aber für zumindest „bedenklich“.

Wenn man beispielsweise mit einem Subscriber die Unterkategorien als Blöcke mit Grafik einbindet… auf einmal hat die mobile Navigation 4 anstatt 2 Ebenen.

Man weiß nie, was in einer for-Schleife wie ausgelesen wird. Um also ungewünschte Nebenwirkungen möglichst zu minimieren, würde ich bei der Extension bleiben. Ist aber nur eine persönliche Meinung.

1 „Gefällt mir“

Ist mir gar nicht aufgefallen das Event. Wenn du erst das ResultEvent abwartest, dann wurde die Repository-Query ja schon ausgeführt und muss ein zweites Mal ausgeführt werden. Wenn du schon beim CriteriaEvent dich einbindest, dann wird Repository-Query nur einmal ausgeführt.

Zumindest ist das mein Verständnis.

1 „Gefällt mir“

Wow, Danke für den Hinweis…Das hört sich voll logisch an… Danke!

In erster Linie ersparst du dir je Produkt eine weitere DB Abfrage:
$productEntity = $this->productRepository->search($criteria, $event->getContext());

Du kannst diese Assoziationen im CriteriaEvent schon einbauen:
$criteria->addAssociation(‚featureSet‘);
$criteria->addAssociation(‚properties‘);

Ich würde mir dann man ein Produkt dumpen, das ResultEvent kann man dazu nutzen um die Daten ggf umzuformatieren.

2 „Gefällt mir“

Vielen Dank für Eure Antworten. Das bringt mich weiter. Sehr interessante Anregungen! Vielen Dank nochmal…

Hi @Moorleiche : Ich habe da mal ne Nachfrage. Wie meinst Du das mit dem „Umformatieren der Daten“… Gibt es da ein Stichwort wonach ich googlen kann? Das hört sich interessant an…

Hi, jau, über das CriteriaEvent lässt sich noch ordentlich was rausholen…Allerdings bekomme ich immer ALLE Produkte zurück, da ich ja über das Criteria noch keine Produkte erhalte und daher meine Anfrage an das Product Repository nicht weiter einschränken kann, oder? Auch der Request ist leer. Hmm…

Hier aber mein Code zur Orientierung. Vielleicht hat jemand ja eine spannende Idee…

    public function onProductListingCriteria(ProductListingCriteriaEvent $event) : void
    {
        $path = '/var/www/html/var/log/debug.json';
        //$this->logger->notice("-->" . var_export($event, true));
        //
        file_put_contents($path, json_encode($event->getCriteria()));
        
        $extensionData = array();
       
        $criteria = $event->getCriteria();
        $criteria->addAssociation('featureSet');
        $criteria->addAssociation('properties');
        $productEntities = $this->productRepository->search($criteria, $event->getContext());
           
        foreach($productEntities as $productEntity){
            $featureSet = $productEntity->getFeatureSet();
            $productId = $productEntity->getId();
            $productProperties = $productEntity->getProperties();
            $extensionData[$productId] = array ();
          
            if ($featureSet != null){
                $features = $featureSet->getFeatures();
                foreach ($features as $feature){
                    $featureId = $feature['id'];
                    $featureName = $feature['name'];

                    if ($featureName == "manufacturerNumber") {
                        array_push($extensionData[$productId],array(
                            "featureName" => "Product Number",
                            "featureValue" => $productEntity->getProductNumber(),
                        ));
                    }
                   
                    foreach ($productProperties as $property ){
                        $propertyId = $property->getGroupId();
                        $propertyName = $property->getname();
                       
                        if ($featureId == $propertyId){
                            array_push($extensionData[$productId],array(
                                "featureName" => $featureName,
                                "featureValue" => $propertyName,
                            ));
                        }
                    }    
                }   
            }
        }
        $event->getCriteria()->addExtension('product_features', new ArrayStruct($extensionData));
    }

Gelöst habe ich das Problem mit der doppelten Search im Repo aber nun doch mit dem ProductResultEvent wie folgt:

    public function onProductListingLoaded(ProductListingResultEvent $event): void
    {
        $path = '/var/www/html/var/log/debug.json';
        $extensionData = array();

        $products = $event->getResult()->getElements();
        $productIds = array();
        foreach($products as $product){
            $productId = $product->getId();
            array_push($productIds, $productId);
        }
        $criteria = new Criteria($productIds);
        $criteria->addAssociation('featureSet');
        $criteria->addAssociation('properties');
        $productEntities = $this->productRepository->search($criteria, $event->getContext());

        foreach ($productEntities as $productEntity) {
            $productId = $productEntity->getId();
            $productProperties = $productEntity->getProperties();
            $extensionData[$productId] = array ();
            
            $featureSet = $productEntity->getFeatureSet();
            if ($featureSet != null){
                $features = $featureSet->getFeatures();
                foreach ($features as $feature){
                    $featureId = $feature['id'];
                    $featureName = $feature['name'];

                    if ($featureName == "manufacturerNumber") {
                        array_push($extensionData[$productId],array(
                            "featureName" => "Product Number",
                            "featureValue" => $productEntity->getProductNumber(),
                        ));
                    }
                   
                    foreach ($productProperties as $property ){
                        $propertyId = $property->getGroupId();
                        $propertyName = $property->getname();
                       
                        if ($featureId == $propertyId){
                            array_push($extensionData[$productId],array(
                                "featureName" => $featureName,
                                "featureValue" => $propertyName,
                            ));
                        }
                    }    
                }   
            }
        }
        $event->getResult()->addExtension('product_features', new ArrayStruct($extensionData));
        file_put_contents($path, json_encode($extensionData));
        $this->logger->notice("onProductListingLoaded -->" . var_export($extensionData, true));
    }

Danke nochmal…