Product-Properties in Produkt-Slider

Moin Zusammen,

nachdem ich bereits die Listings um Properties und Hersteller-Daten erweitert habe, möchte ich auch Produkte die in Erlebniswelt-Slidern um diese Daten erweitern. Hierzu haben mir bisher hauptsächlich diese beidem Threads geholfen (danke!):

https://forum.shopware.com/discussion/62944/daten-von-subscriber-controller-an-cms-element-uebergeben
https://forum.shopware.com/discussion/68787/wie-bekomme-ich-properties-in-cms-element-mit-produkt

Mein collect sieht aktuell so aus:

class SliderElementResolver extends ProductSliderCmsElementResolver
{
    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $criteriaCollection = new CriteriaCollection();

        $config = $slot->getFieldConfig();
        $products = $config->get('products');

        if (!$products || $products->isMapped() || $products->getValue() === null) {
            return null;
        }

        if ($products->isStatic() && $products->getValue()) {
            $criteria = new Criteria($products->getValue());
            $criteria->addAssociation('properties.group');
            $criteria->addAssociation('manufacturer.media');
            $criteriaCollection->add('product-slider' . '_' . $slot->getUniqueIdentifier(), ProductDefinition::class, $criteria);
        }

        return $criteriaCollection->all() ? $criteriaCollection : null;
    }
}

Die Gruppierung der Daten erfolgt im bereits vorhandenen ProductEvents::PRODUCT_LOADED_EVENT Listener.

Eigentlich funktioniert das auch so. Was mich nur wundert ist, dass in $criteriaCollection->add der erste Parameter $key von mir hardcecodet mit product-slider geprefixt werden musste, obwohl hierfür in der Eltern-Klasse ProductSliderCmsElementResolver eine Konstante STATIC_SEARCH_KEY = "product-slider" existiert und diese dort auch als Prefix für $key genutzt wird.

Diese ist aber private, nicht protected und kann somit nicht genutzt werden. So ist das Ganze auf meiner Seite nicht sonderlich Update-resistent. Es gibt auch noch ein getType(), was ebenfalls ‚product-slider‘ zurückgibt - aber ebenfalls hard und nicht per STATIC_SEARCH_KEY.

Ist das von Shopware unsauber programmiert, oder mache ich hier grundsätzlich etwas falsch? (Worauf mich das private ja vielleicht hinweisen möchte…) Frown

Besten Dank,
devnulroot

Moin Zusammen,

als Hinweis: Die grundsätzliche Herangehensweise ist mir beim Update auf die 6.3.1.0 auf die Füße gefallen, weshalb ich dank @Shyim‍ nun Dependency Injection benutze. Falls noch mal wer hierdrüber stolpert:

class MyResolver
{
    /** @var ProductSliderCmsElementResolver $elementResolver */
    private $elementResolver;

    public function __construct(ProductSliderCmsElementResolver $elementResolver)
    {
        $this->elementResolver = $elementResolver;
    }

    public function getType(): string
    {
        return $this->elementResolver->getType();
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $criteriaCollection = new CriteriaCollection();

        $config = $slot->getFieldConfig();
        $products = $config->get('products');

        if (!$products || $products->isMapped() || $products->getValue() === null) {
            return null;
        }

        if ($products->isStatic() && $products->getValue()) {
            $criteria = new Criteria($products->getValue());
            $criteria->addAssociation('properties.group');
            $criteria->addAssociation('manufacturer.media');
            $criteriaCollection->add('product-slider' . '_' . $slot->getUniqueIdentifier(), ProductDefinition::class, $criteria);
        }
        return $criteriaCollection->all() ? $criteriaCollection : null;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $this->elementResolver->enrich($slot, $resolverContext, $result);
    }
}

services.xml Auszug:

Beste Grüße,
devnullroot

1 „Gefällt mir“

@devnullroot‍ Danke für deinen Code. Hast du deinen Service über das Decorator Pattern registriert? Falls nein, muss noch etwas anderes in services.xml stehen?

Sorry, hab grade so gar keine Zeit, aber der Code sollte, bis auf Namensanpassungen, genau so laufen. Bei mir ist jedenfalls nicht mehr drin, hab eben kurz nachgesehen.

Beste Grüße,
devnullroot

Kurzer Nachtrag: Der Code funktioniert NICHT bei Slidern mit dynamischen Produktgruppen. Hinter der if Abfrage mit isStatic() habe ich als Fallback die Original-Methode aufgerufen:

else {
    $criteriaCollection = $this->elementResolver->collect($slot, $resolverContext);
}

Die dynamischen Produktgruppen werden da über die private Methode “collectByProductStream” abgefrühstückt. Die habe ich nicht angepasst/überschrieben und auch keine Möglichkeit gefunden hier $criteria zu erweitern :frowning:

Beste Grüße,
devnullroot

Ich habe heute diese Funktion ebenfalls benötigt. Vielen dank für die Ausführung. Es ist aber grundsätzlich nicht nötig den collect komplett neu zu definieren. Hier besteht aus meiner sicht auch eher das Problem das bei einem update der collect im zweifels fall auch angepasst werden muss. es Funktioniert auch mit dem Parent collect welcher dann nur um die Association erweiter

public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
{
    $criteriaCollection = $this->resolver->collect($slot, $resolverContext);

    foreach ($criteriaCollection as $productCriteria) {
        foreach ($productCriteria as $criteria) {
            $criteria->addAssociation('properties.group');
        }
    }

    return $criteriaCollection;
}

hier sollten dann auch die Probleme mit den Privaten Klassen Constanten nicht mehr existieren und bei mir funktioniert auch ein dynamische Produkt Gruppe

2 „Gefällt mir“

Moin @Stephan4p ,

super, vielen Dank für die wesentlich bessere und elegantere Implementierung!

Beste Grüße,
devnullroot

Moin zusammen,
ich bin auch gerade dabei einen Resolver mit den Decorator Pattern zu erweitern ich möchte das Hersteller-Logo rein bekommen. Also die URL dazu. Leider wird mir die URL bisher noch nicht ausgeben.

Bisher habe ich das hier gebaut:

        $criteria->addAssociation('manufacturer.media');

        $criteriaCollection = new CriteriaCollection();
        $criteriaCollection->add('product_' . $slot->getUniqueIdentifier(), ProductDefinition::class, $criteria);

Ein dump($criteriaCollection) gibt mir nur leere Felder bei manufacturer.media aus.
Wie kann ich die Werte bekommen?
Muss ich einen API call machen?

Hallo,

ich stelle gerade fest, dass der Produkt-Slider, der vom Cross Selling verwendet wird, in der neuen Version (6.4.x) Probleme macht. Vorher konnte der Herstellername dort angezeigt werden, jetzt nicht mehr.
Ob dynamische Produktgruppe oder nicht ist nach meinen Tests egal.

Weiß jemand, was geändert muss, bzw. was sich seit Shopware 6.4 verändert hat?

Edit: Hier nochmal der Inhalt des Product Slider Resolvers:

<?php

declare(strict_types=1);

namespace SupplierInfoListing\Resolver;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Content\Product\ProductDefinition;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;

use Shopware\Core\Content\Product\Cms\ProductSliderCmsElementResolver;

class SupplierInfoListingProductSlider
{
  /** @var ProductSliderCmsElementResolver $elementResolver */
  private $elementResolver;

  public function __construct(ProductSliderCmsElementResolver $elementResolver)
  {
    $this->elementResolver = $elementResolver;
  }

  public function getType(): string
  {
    return $this->elementResolver->getType();
  }
  
  public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
  {
    $criteriaCollection = $this->elementResolver->collect($slot, $resolverContext);

    foreach ($criteriaCollection as $productCriteria) {
        foreach ($productCriteria as $criteria) {
            $criteria->addAssociation('manufacturer');
            $criteria->addAssociation('manufacturer.media');
        }
    }

    return $criteriaCollection;
  }


  public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
  {
    $this->elementResolver->enrich($slot, $resolverContext, $result);
  }
}

LG

Moin @cinquecento,

ohne es getestet zu haben: Ich denke Du brauchst einen CrossSellingCmsElementResolver und keinen ProductSliderCmsElementResolver.

Beste Grüße,
devnullroot

Danke für den Hinweis auf den CrossSellingCmsElementResolver @devnullroot. Das muss neu sein, wenn ich das richtig sehe (Eintrag im Changelog: platform/2020-12-10-display-cross-selling-element-on-storefront.md at trunk · shopware/platform · GitHub).

In der Originaldatei Shopware\Core\Content\Product\Cms\CrossSellingCmsElementResolver gibt es nun keine collect function.

Hat jemand einen Tipp, wie die collect function hier aussehen müsste?

Grüße!

Hallo, ich bin neu in der Welt von Shopware und versuche ein Theme nach Designvorlage umzusetzen. Nun bin ich auf das gleiche Probleme wie oben beschrieben gestoßen. Ich brauch den manufacturer name in eine ProductBox, bzw. zukünftig auch in einem Product Slider. Die ProductBoxResolver Klasse hab ich analog zu hier beschriebenen SliderResolver klasse angelegt.

Wie muss die service.xml aussehen, damit der Service richtig registriert wird?
Könnte mir jemand auf die Sprünge helfen oder mir einen Tipp geben, wo ich weitere Informationen dazu finde?

Vielen Dank und liebe Grüße!

Hi @plusgrad,

meine service.xml sieht so aus:

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <services>
        <service id="SupplierInfoListing\Subscriber\SupplierInfoListingManufacturerListing">
            <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService" />
            <argument type="service" id="service_container"/>
            <tag name="kernel.event_subscriber"/>
        </service>
        <service id="SupplierInfoListing\Subscriber\SupplierInfoListingManufacturerSearch">
            <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService" />
            <argument type="service" id="service_container"/>
            <tag name="kernel.event_subscriber"/>
        </service>
        <service id="SupplierInfoListing\Resolver\SupplierInfoListingProductSlider">
            <tag name="shopware.cms.data_resolver"/>
            <argument type="service" id="Shopware\Core\Content\Product\Cms\ProductSliderCmsElementResolver"/>
        </service>
        <service id="SupplierInfoListing\Resolver\SupplierInfoListingProductBox">
            <tag name="shopware.cms.data_resolver"/>
            <argument type="service" id="Shopware\Core\Content\Product\Cms\ProductBoxCmsElementResolver"/>
        </service>
    </services>
</container>

Die service IDs und die classes SupplierInfoListingProductBox, SupplierInfoListingProductSlider, SupplierInfoListingManufacturerListing und SupplierInfoListingManufacturerSearch heißen bei dir wahrscheinlich anders, das musst du dann anpassen.

LG

Hallo!

@cinquecento stehe vor dem gleichen Problem. Hast du hier inzwischen eine Lösung gefunden?

Viele Grüße

Moin @SteVo,

leider bin ich mit dem CrossSellingCmsElementResolver bisher nicht weitergekommen.
Ich bin noch nicht so firm mit Shopware 6, dass ich das Problem alleine lösen kann.

Vielleicht kann @devnullroot ja helfen?

Liebe Grüße

Wieso gibt es hier kein Criteria-Event?

Hallo @cinquecento,

habe es nun über einen anderen Weg gelöst.

Hier der Eintrag aus der service.xml:

<service id="[dein-namespace]\CachedProductCrossSellingRouteExtended" 
                decorates="Shopware\Core\Content\Product\SalesChannel\CrossSelling\CachedProductCrossSellingRoute">
                <argument type="service" id="Shopware\Core\Content\Product\SalesChannel\CrossSelling\CachedProductCrossSellingRoute.inner"/>
                <argument type="service" id="cache.object"/>
                <argument type="service" id="Shopware\Core\Framework\DataAbstractionLayer\Cache\EntityCacheKeyGenerator"/>
                <argument type="service" id="Shopware\Core\Framework\Adapter\Cache\CacheTracer"/>
                <argument type="service" id="event_dispatcher"/>
                <argument>%shopware.cache.invalidation.product_cross_selling_route%</argument>
                <argument type="service" id="logger"/>
        </service>
<?php declare(strict_types=1);

namespace [dein-namespace]\Service;

use Shopware\Core\Content\Product\SalesChannel\CrossSelling\CachedProductCrossSellingRoute;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Symfony\Component\HttpFoundation\Request;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\Content\Product\SalesChannel\CrossSelling\ProductCrossSellingRouteResponse;

class CachedProductCrossSellingRouteExtended extends CachedProductCrossSellingRoute
{

	public function load(string $productId, Request $request, SalesChannelContext $context, Criteria $criteria) : ProductCrossSellingRouteResponse
	{

		$criteria->addAssociation('manufacturer');
        $criteria->addAssociation('properties.group');

		return parent::load($productId, $request, $context, $criteria);

	}

}

Vielleicht hilft es ja :slight_smile:

Viele Grüße!

3 „Gefällt mir“

ich habe auch Probleme, ich habe alles mögliche ausprobiert aber mein Subscriber befüllt die Daten nicht, ich möchte die Produkt-Slider in Erlebniswelten (mit dynamische Produktgruppen befüllt) erweitern. Der Slider greift ja auf die box-standard und wenn ich da ein dump auf die Produkte mache, dann bekommen ich bei Manufacturer z.B. null.

Meine Subscriber Events die ich bis jetzt ausprobiert habe:

            ProductEvents::PRODUCT_LOADED_EVENT => "onProductLoaded",
            ProductBoxCmsElementResolver::PRODUCT_LOADED_EVENT => "onProductBoxCmsLoaded",
            ProductSliderCmsElementResolver::PRODUCT_LOADED_EVENT => "onProductSliderCmsLoaded",
            ProductListingCriteriaEvent::PRODUCT_LOADED_EVENT => "onProductListingCriteriaEvent",
            ProductLoaderCriteriaEvent::class => 'productLoaderCriteria',
            'sales_channel.product.loaded' => 'onSalesChannelEntityLoadedEvent'

hey ich kann dir da leider nicht weiterhelfen. aber vielleicht hilft dir das hier, ich konnte mein Problem lösen:

Ich habe hiermit noch ein generelles Problem… wenn zwei Plugins ihre Associations hinzufügen wollen, überschreibt eines das andere, oder? Beispiel:

  1. Plugin holt sich eine Association auf manufacturer.media rein:
<?php declare(strict_types=1);

namespace foobar1\Core\Content\Product\Cms;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Product\Cms\ProductSliderCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;

class ManufacturerMediaProductSliderCmsElementResolver
{
    /** @var ProductSliderCmsElementResolver $elementResolver */
    private $elementResolver;

    public function __construct(ProductSliderCmsElementResolver $elementResolver)
    {
        $this->elementResolver = $elementResolver;
    }

    public function getType(): string
    {
        return $this->elementResolver->getType();
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $criteriaCollection = $this->elementResolver->collect($slot, $resolverContext);

        foreach ($criteriaCollection as $productCriteria) {
            foreach ($productCriteria as $criteria) {
                $criteria->addAssociation('manufacturer.media');
            }
        }

        return $criteriaCollection;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $this->elementResolver->enrich($slot, $resolverContext, $result);
    }
}

Das 2. Plugin holt sich eine Association auf options.group rein.

<?php declare(strict_types=1);

namespace foobar2\Core\Content\Product\Cms;

use Shopware\Core\Content\Cms\Aggregate\CmsSlot\CmsSlotEntity;
use Shopware\Core\Content\Product\Cms\ProductSliderCmsElementResolver;
use Shopware\Core\Content\Cms\DataResolver\Element\ElementDataCollection;
use Shopware\Core\Content\Cms\DataResolver\ResolverContext\ResolverContext;
use Shopware\Core\Content\Cms\DataResolver\CriteriaCollection;

class OptionsProductSliderCmsElementResolver
{
    /** @var ProductSliderCmsElementResolver $elementResolver */
    private $elementResolver;

    public function __construct(ProductSliderCmsElementResolver $elementResolver)
    {
        $this->elementResolver = $elementResolver;
    }

    public function getType(): string
    {
        return $this->elementResolver->getType();
    }

    public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
    {
        $criteriaCollection = $this->elementResolver->collect($slot, $resolverContext);

        foreach ($criteriaCollection as $productCriteria) {
            foreach ($productCriteria as $criteria) {
                $criteria->addAssociation('options.group');
            }
        }

        return $criteriaCollection;
    }

    public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
    {
        $this->elementResolver->enrich($slot, $resolverContext, $result);
    }
}

Im Ergebnis ist dann die Association auf manufacturer.media des Plugin 1 futsch… Ist das so, oder mache ich hier einen Fehler?