Eigene Artikel im ListProductService

Hallo,

ich möchte mein Artikel-Listing gern durch ein paar dynamische Artikel erweitern, die von einer externen API abgerufen werden.

Daher habe ich mir überlegt, einen Decorator für den ListProductService zu schreiben, der nach dem Abrufen der Shopware Artikel die entsprechenden Artikel aus der API anfügt:

class CustomProductService implements ListProductServiceInterface
{

    function __construct(ListProductServiceInterface $service)
    {
        $this->service = $service;
    }

    public function getList(array $numbers, Struct\ProductContextInterface $context)
    {

        $coreProducts = $this->service->getList($numbers, $context);

        $customProduct = new \Shopware\Bundle\StoreFrontBundle\Struct\ListProduct;
        $customProduct->setName('Testartikel 1');
        // ... weitere Eigenschaften füllen ...

        $customProducts = [$customProduct];

        return array_merge($coreProducts, $customProducts);

    }

}

Das funktioniert jedoch nicht, es kommt folgende Fehlermeldung:

Fatal error : Uncaught Error: Call to a member function getId() on null in /var/www/woospa.de/engine/Shopware/Components/Compatibility/LegacyStructConverter.php:1135

Das ist auch soweit logisch, denn es wurde ja gar keine ID festgelegt. Um eine ID festlegen zu können (das wäre eine erkennbare Dummy-ID, wie “99999”), müsste ich ja theoretisch das BaseProduct (\Shopware\Bundle\StoreFrontBundle\Struct\BaseProduct) erstellen. In diesem kann die ID festgelegt werden. Jedoch habe ich dann keinen Zugriff mehr auf die Methoden des ListProduct, also Name, Attribute, Preise, etc.

Wie kann ich dies lösen?

Vielen Dank für Eure Hilfe!

@floatax schrieb:

[…]. Um eine ID festlegen zu können (das wäre eine erkennbare Dummy-ID, wie “99999”), müsste ich ja theoretisch das BaseProduct (\Shopware\Bundle\StoreFrontBundle\Struct\BaseProduct) erstellen. In diesem kann die ID festgelegt werden. Jedoch habe ich dann keinen Zugriff mehr auf die Methoden des ListProduct, also Name, Attribute, Preise, etc.[…]

Das ist falsch. Da die Klasse ListProduct die Klasse BaseProduct erweitert, stehen dir in ListProduct alle Methoden aus BaseProduct zur Verfügung.

Viele Grüße  

Vielen Dank für die Antwort, leider weiß ich absolut nicht, wie ich bei dem neu angelegten Produkt die ID festlege.

Übergebe ich die ID dem Constructor, findet bekomme ich den Fatal Error, weil scheinbar die BaseProduct Klasse nicht richtig impliziert wird:

$customProduct = new \Shopware\Bundle\StoreFrontBundle\Struct\ListProduct('9999');
$customProduct->setName('Testartikel 1');

Fatal error : Uncaught Error: Call to a member function getId() on null in /var/www/woospa.de/engine/Shopware/Components/Compatibility/LegacyStructConverter.php:1135

Da es in dem BaseProduct aber auch keine Methode zum Festlegen einer ID gibt, funktioniert es so also auch nicht: 

$customProduct = new \Shopware\Bundle\StoreFrontBundle\Struct\ListProduct;
$customProduct->setID('9999');
$customProduct->setName('Testartikel 1');

Moin @floatax‍!

Vielleicht verstehe ich hier etwas falsch, aber:
Die Fehlermeldung sagt  nicht , dass du keine ID gesetzt hast - die Fehlermeldung sagt nur, dass versucht wird „getId“ auf ‚null‘ aufzurufen.

Was steht in der genannten Zeile?

'taxID' => $product->getTax()->getId(),

Er versucht von deinem Produkt auf die Steuer zuzugreifen, und auf der Steuer dann „getId“ auszuführen.
Dein Produkt liefert jedoch beim Aufruf von „getTax“ schon ‚null‘ zurück, sodass eben die genannte Fehlermeldung auftaucht.
Das hat erstmal nichts damit zu tun, dass dein Produkt selbst keine ID hat.

Verstehst du das eigentliche Problem?

Im Konstruktor des ListProducts kannst du übrigens durchaus die ID angeben, so wie du es schon gemacht hast.
Als 2. Parameter nimmt es die VariantId und als 3. Parameter die Bestellnummer.

Gruß,
Patrick  Shopware

Oh, ja natürlich. Ich hätte schwören können, dass ich mit den LegacyStructConverter angesehen habe. Er hat natürlich gemeckert, weil ich zunächst nur versucht habe, einen Namen und eine ID festzulegen - die restlichen Details, wie Tax, Price, Media, etc. habe ich vorerst weggelassen. Und tatsächlich bezog sich das getId() auf die Tax-Gruppe. Vielen Dank für den Hinweis!

Nichtsdestotrotz ist mir dabei aufgefallen, dass hier vielleicht der falsche Weg ist, um eigene Artikel dem Listing hinzuzufügen - da man hier eben die komplette Artikel-Struktur korrekt abbilden muss, damit diese wiederum durch den Converter geht.

Was wäre Eurer Meinung nach die bessere Methode, um die eigenen Produkte ins Listing zu geben? Ein Hook auf sArticles::sGetArticlesByCategory::after? Oder doch lieber Events auf die einzelnen Actions im Frontend (Listing, Search, etc.)?

Update: Ich hab es nun doch hinbekommen. Im Listing hatte ich einen anderen Weg gefunden. Als die Suchfunktion jedoch ebenfalls auf die externe API zugreifen musste, musste ich den SearchService dekorieren und stand wieder vor dem gleichen Problem.

Für alle, die ebenfalls dieses Problem haben, hier eine recht einfache Lösung, im Listing- oder SearchService eigene Produkte hinzuzufügen:

use Shopware\Bundle\SearchBundle;
use Shopware\Bundle\StoreFrontBundle;
use Shopware\Bundle\SearchBundle\ProductSearch;
use Shopware\Bundle\SearchBundle\ProductSearchInterface;
use Shopware\Bundle\SearchBundle\Condition\ProductAttributeCondition;
use Shopware\Bundle\SearchBundle\ProductSearchResult;
use Shopware\Bundle\StoreFrontBundle\Struct\ListProduct;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\Price;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\PriceRule;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\Unit;
use Shopware\Bundle\StoreFrontBundle\Struct\Media;
use Shopware\Bundle\StoreFrontBundle\Struct\Thumbnail;

class CustomSearchService implements ProductSearchInterface
{

    private $productSearch;

    public function __construct(ProductSearchInterface $productSearch)
    {
    	$this->productSearch = $productSearch;
    }

    public function search(\Shopware\Bundle\SearchBundle\Criteria $criteria, \Shopware\Bundle\StoreFrontBundle\Struct\ProductContextInterface $context)
    {

        $searchResult = $this->productSearch->search($criteria, $context);
        $products = $searchResult->getProducts();

        $unit = new Unit;
        $unit->setReferenceUnit(1);
        $unit->setPackUnit(0);
        $unit->setMinPurchase(1);

        $priceRule = new PriceRule;
        $priceRule->setId(0);
        $priceRule->setPrice(19.95);
        $priceRule->setFrom(1);
        $priceRule->setPseudoPrice(0);
        $priceRule->setCustomerGroup($context->getCurrentCustomerGroup());
        $priceRule->setUnit($unit);

        $price = new Price($priceRule);
        $price->setCalculatedPrice(19.95);
        $price->setCalculatedPseudoPrice(0);

        $cover = new Media;
        $cover->setId(0);
        $cover->setFile('url_to_file.jpg');
        $cover->setThumbnails([
            new Thumbnail(
                'url_to_file.jpg',
                'url_to_file.jpg',
                200,
                200
            )
        ]);

        $product = new ListProduct('9999', '9999', '9999');
        $product->setTax($context->getTaxRule(1));
        $product->setCheapestUnitPrice($price);
        $product->setName('Testartikel 1');
        $product->setLongDescription('bla bla');
        $product->setShortDescription('bla bla');
        $product->setCover($cover);

        $products[] = $product;

        $searchResult = new ProductSearchResult($products, count($products), $searchResult->getFacets());

        return $searchResult;

    }

}

Das Beispiel ist jetzt natürlich seeeeehr vereinfacht und muss dementsprechend angepasst werden.

1 Like

@floatax schrieb:

Update: Ich hab es nun doch hinbekommen. Im Listing hatte ich einen anderen Weg gefunden. Als die Suchfunktion jedoch ebenfalls auf die externe API zugreifen musste, musste ich den SearchService dekorieren und stand wieder vor dem gleichen Problem.

Für alle, die ebenfalls dieses Problem haben, hier eine recht einfache Lösung, im Listing- oder SearchService eigene Produkte hinzuzufügen:

use Shopware\Bundle\SearchBundle;
use Shopware\Bundle\StoreFrontBundle;
use Shopware\Bundle\SearchBundle\ProductSearch;
use Shopware\Bundle\SearchBundle\ProductSearchInterface;
use Shopware\Bundle\SearchBundle\Condition\ProductAttributeCondition;
use Shopware\Bundle\SearchBundle\ProductSearchResult;
use Shopware\Bundle\StoreFrontBundle\Struct\ListProduct;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\Price;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\PriceRule;
use Shopware\Bundle\StoreFrontBundle\Struct\Product\Unit;
use Shopware\Bundle\StoreFrontBundle\Struct\Media;
use Shopware\Bundle\StoreFrontBundle\Struct\Thumbnail;

class CustomSearchService implements ProductSearchInterface
{

private $productSearch;

public function __construct(ProductSearchInterface $productSearch)
{
$this->productSearch = $productSearch;
}

public function search(\Shopware\Bundle\SearchBundle\Criteria $criteria, \Shopware\Bundle\StoreFrontBundle\Struct\ProductContextInterface $context)
{

$searchResult = $this->productSearch->search($criteria, $context);
$products = $searchResult->getProducts();

$unit = new Unit;
$unit->setReferenceUnit(1);
$unit->setPackUnit(0);
$unit->setMinPurchase(1);

$priceRule = new PriceRule;
$priceRule->setId(0);
$priceRule->setPrice(19.95);
$priceRule->setFrom(1);
$priceRule->setPseudoPrice(0);
$priceRule->setCustomerGroup($context->getCurrentCustomerGroup());
$priceRule->setUnit($unit);

$price = new Price($priceRule);
$price->setCalculatedPrice(19.95);
$price->setCalculatedPseudoPrice(0);

$cover = new Media;
$cover->setId(0);
$cover->setFile(‚url_to_file.jpg‘);
$cover->setThumbnails([
new Thumbnail(
‚url_to_file.jpg‘,
‚url_to_file.jpg‘,
200,
200
)
]);

$product = new ListProduct(‚9999‘, ‚9999‘, ‚9999‘);
$product->setTax($context->getTaxRule(1));
$product->setCheapestUnitPrice($price);
$product->setName(‚Testartikel 1‘);
$product->setLongDescription(‚bla bla‘);
$product->setShortDescription(‚bla bla‘);
$product->setCover($cover);

$products = $product;

$searchResult = new ProductSearchResult($products, count($products), $searchResult->getFacets());

return $searchResult;

}

}

Das Beispiel ist jetzt natürlich seeeeehr vereinfacht und muss dementsprechend angepasst werden.

 

Danke für das einfache Beispiel. Allerdings habe ich ein Problem (Shopware 5.4):

Uncaught ArgumentCountError: Too few arguments to function MyPlugin\Bundle\SearchBundle\ProductSearch::__construct()

Ist das Beispiel mit der akutellen Version nicht kompatibel? 

Hallo @Vmadmax‍ ,

nein, in Shopware 5.4 hatten sich dort ein paar Änderungen ergeben. Das ProductSearchResult erwartet nun noch die Criteria und den Shop Context im Constructor. Da die ja bereits als Argumente an die search Funktion in meinem Beispiel übergeben wurden, können wir sie einfach mit durchschleusen.

Ersetze einfach

$searchResult = new ProductSearchResult($products, count($products), $searchResult->getFacets());

damit:

$searchResult = new ProductSearchResult($products, count($products), $searchResult->getFacets(), $criteria, $context);

Hoffe, es klappt.

1 Like