If you want to change price of Items in cart then you can use a cart collector and a cart processor.
The collector
The Collector collects all the necessary data for line items. It will also take care of reducing duplicated requests, but we’ll get into that later.
This guide will not cover where to actually fetch the new prices from, that’s up to you. This could e.g. be an extension of the product entity, which contains the new price, or an API call from somewhere else, which will return the new price.
Your collector class has to implement the interface Shopware\Core\Checkout\Cart\CartDataCollectorInterface and therefore the method collect.
<?php declare(strict_types=1);
namespace ICT\BasicExample\Core\Checkout\Cart; use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\CartBehavior; use Shopware\Core\Checkout\Cart\CartDataCollectorInterface; use Shopware\Core\Checkout\Cart\LineItem\CartDataCollection; use Shopware\Core\Checkout\Cart\LineItem\LineItem; use Shopware\Core\System\SalesChannel\SalesChannelContext; class OverwritePriceCollector implements CartDataCollectorInterface { public function collect(CartDataCollection $data, Cart $original, SalesChannelContext $context, CartBehavior $behavior): void { // get all product ids of current cart $productIds = $original->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->getReferenceIds(); // remove all product ids which are already fetched from the database $filtered = $this->filterAlreadyFetchedPrices($productIds, $data); // Skip execution if there are no prices to be requested & saved if (empty($filtered)) { return; } foreach ($filtered as $id) { $key = $this->buildKey($id); // Needs implementation, just an example $newPrice = $this->doSomethingToGetNewPrice(); // we have to set a value for each product id to prevent duplicate queries in next calculation $data->set($key, $newPrice); } } private function filterAlreadyFetchedPrices(array $productIds, CartDataCollection $data): array { $filtered = []; foreach ($productIds as $id) { $key = $this->buildKey($id); // already fetched from database? if ($data->has($key)) { continue; } $filtered[] = $id; } return $filtered; } private function buildKey(string $id): string { return ‘price-overwrite-‘.$id; } } |
Read More: Shopware 6 Plugin: Cross-Selling and Up-Selling in the Cart
So the example class is called OverwritePriceCollector here and it implements the method collect. This method’s parameters are the following:
- CartDataCollection: This is the object, that will contain our new data, which is then processed in the processor.
Here you’re going to save the new price. It contains key-value pairs, so we will save the new price as the value, it’s key being the line item ID. We will prefix a custom string to the line item ID, so our code will not interfere with other collectors, that might also save the line item ID as a key.
- Cart: Well, the current cart and its line items.
- SalesChannelContext: The current sales channel context, containing information about the currency, the country, etc.
- CartBehavior: It contains cart permissions, which are not necessary for our example.
The processor
The processor is only used for collecting prices and calculating them appropriately and applying them to the line items.
The processor now has to fetch the new prices from the CartDataCollector and it has to calculate the actual new price of that line item, e.g. due to taxes. For this case, it will need the Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator.
<?php declare(strict_types=1);
namespace ICT\BasicExample\Core\Checkout\Cart; use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\CartBehavior; use Shopware\Core\Checkout\Cart\CartDataCollectorInterface; use Shopware\Core\Checkout\Cart\CartProcessorInterface; use Shopware\Core\Checkout\Cart\LineItem\CartDataCollection; use Shopware\Core\Checkout\Cart\LineItem\LineItem; use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator; use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition; use Shopware\Core\System\SalesChannel\SalesChannelContext; class OverwritePriceCollector implements CartDataCollectorInterface, CartProcessorInterface { private QuantityPriceCalculator $calculator; public function __construct(QuantityPriceCalculator $calculator) { $this->calculator = $calculator; } public function collect(CartDataCollection $data, Cart $original, SalesChannelContext $context, CartBehavior $behavior): void { // get all product ids of current cart $productIds = $original->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->getReferenceIds(); // remove all product ids which are already fetched from the database $filtered = $this->filterAlreadyFetchedPrices($productIds, $data); // Skip execution if there are no prices to be saved if (empty($filtered)) { return; } foreach ($filtered as $id) { $key = $this->buildKey($id); // Needs implementation, just an example $newPrice = $this->doSomethingToGetNewPrice(); // we have to set a value for each product id to prevent duplicate queries in next calculation $data->set($key, $newPrice); } } public function process(CartDataCollection $data, Cart $original, Cart $toCalculate, SalesChannelContext $context, CartBehavior $behavior): void { // get all product line items $products = $toCalculate->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE); foreach ($products as $product) { $key = $this->buildKey($product->getReferencedId()); // no overwritten price? continue with next product if (!$data->has($key) || $data->get($key) === null) { continue; } $newPrice = $data->get($key); // build new price definition $definition = new QuantityPriceDefinition( $newPrice, $product->getPrice()->getTaxRules(), $product->getPrice()->getQuantity() ); // build CalculatedPrice over calculator class for overwritten price $calculated = $this->calculator->calculate($definition, $context); // set new price into line item $product->setPrice($calculated); $product->setPriceDefinition($definition); } } private function filterAlreadyFetchedPrices(array $productIds, CartDataCollection $data): array { $filtered = []; foreach ($productIds as $id) { $key = $this->buildKey($id); // already fetched from database? if ($data->has($key)) { continue; } $filtered[] = $id; } return $filtered; } private function buildKey(string $id): string { return ‘price-overwrite-‘.$id; } } |
First of all, note the second interface we implemented, next to the CartDataCollectorInterface. We also added a constructor in order to inject the QuantityPriceCalculator.
But now, let’s have a look at the process method. You should already be familiar with most of its parameters since they’re mostly the same with those of the collector. Yet, there’s one main
difference: Next to the $original Cart, you’ve got another Cart parameter being called $toCalculatehere. Make sure to do all the changes on the $toCalculate instance, since this is the cart that’s going to be considered in the end. The $original one is just there, because it may contain necessary data for the actual cart instance.
Now let’s have a look inside the process method.
We start by filtering all line items down to only products, just like we did in the collect method. Then we’re iterating over all products found, building the unique key, which is necessary for fetching the new price from the CartDataCollector.
If there’s no price to be processed saved in the CartDataCollector, there’s nothing to do here. Otherwise, we’re fetching the new price, we’re building a new instance of a QuantityPriceDefinition containing the new price. Using that instance, we can calculate the actual new price using the previously injected QuantityPriceCalculator.
NOTE: Do not query the database in the process method. Make sure to always use a collector for that.