Add cart validator

Overview

The cart in Shopware is constantly being validated by so called "validators". This way we can check for an invalid cart, e.g. for invalid line items (label missing) or an invalid shipping address.
This guide will cover the subject on how to add your own custom cart validator.

Prerequisites

For this guide, you'll need a working plugin, which you learn to create here. Also, you'll have to know the Dependency Injection container, since that's going to be used in order to register your custom validator.

Adding a custom cart validator

We'll create several things throughout this guide, in that order:
  • The validator itself
  • A new exception being thrown by the validator if needed
  • Snippets to print a proper error message

The validator

The validator being created in this example is assuming you've got custom payload data in your line items to validate against. This is just an example and will always result in an error, since the data requested doesn't exist by default, until you add them.
A validator should be placed in the proper domain. That means, that an Address validator should be in a directory <plugin root>/src/Core/Checkout/Cart/Address. Since the validator in the following example will be called CustomCartValidator, its directory will be <plugin root>/src/Core/Checkout/Cart/Custom.
Your validator has to implement the interface Shopware\Core\Checkout\Cart\CartValidatorInterface. This forces you to also implement a validate method.
But let's have a look at the example validator first:
<plugin root>/src/Core/Checkout/Cart/Custom/CustomCartValidator.php
1
<?php declare(strict_types=1);
2
3
namespace Swag\BasicExample\Core\Checkout\Cart\Custom;
4
5
use Shopware\Core\Checkout\Cart\Cart;
6
use Shopware\Core\Checkout\Cart\CartValidatorInterface;
7
use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
8
use Shopware\Core\System\SalesChannel\SalesChannelContext;
9
use Swag\BasicExample\Core\Checkout\Cart\Custom\Error\CustomCartBlockedError;
10
11
class CustomCartValidator implements CartValidatorInterface
12
{
13
public function validate(Cart $cart, ErrorCollection $errorCollection, SalesChannelContext $salesChannelContext): void
14
{
15
foreach ($cart->getLineItems()->getFlat() as $lineItem) {
16
if (!array_key_exists('customPayload', $lineItem->getPayload()) || $lineItem->getPayload()['customPayload'] !== 'example') {
17
$errorCollection->add(new CustomCartBlockedError($lineItem->getId()));
18
19
return;
20
}
21
}
22
}
23
}
Copied!
As already said, a cart validator has to implement the CartValidatorInterface and therefore implement a validate method. This method has access to some important parts of the checkout, such as the cart and the current sales channel context. Also you have access to the error collection, which may or may not contain errors from other earlier validators.
In this example we're dealing with the line items and are validating them, so we're iterating over each line item. This example assumes that your line items got a custom payload, called customPayload, and it expects a value in there.
If the condition doesn't match and the line item seems to be invalid, you'll have to add a new error to the error collection. You can't just use any exception here, but a class which has to extend from Shopware\Core\Checkout\Cart\Error\Error. Most likely you want to create your own error class here, which will be done in the next step.
Important to note is the return statement afterwards. If you wouldn't return here, it would add an error to the error collection for each invalid line item, resulting in several errors displayed on the checkout or the cart page. E.g. if you had four invalid items in your cart, four separate errors would be shown. This way, only one message is shown, so it depends on what you're validating and what you want to happen.

Registering the validator

One more thing to do is to register your new validator to the dependency injection container.
Your validator has to be registered using the tag shopware.cart.validator:
<plugin root>/src/Resources/config/services.xml
1
<?xml version="1.0" ?>
2
<container xmlns="http://symfony.com/schema/dic/services"
3
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
5
6
<services>
7
<service id="Swag\BasicExample\Core\Checkout\Cart\Custom\CustomCartValidator">
8
<tag name="shopware.cart.validator"/>
9
</service>
10
</services>
11
</container>
Copied!

Adding the custom cart error

The custom cart error class will be called CustomCartBlockedError and should be located in a Error directory in the same domain as the validator. Since the validator was located in the directory <plugin root>/src/Core/Checkout/Cart/Custom, the error class will be located in the directory <plugin root>/src/Core/Checkout/Cart/Custom/Error.
It has to extend from the abstract class Shopware\Core\Checkout\Cart\Error\Error, which asks you to implement a few methods:
  • getId: Here you have to return a unique ID, since your error will be saved via this ID in the error collection. In this example,
    we'll just use the line item ID here.
  • getMessageKey: The snippet key of the message to be displayed. In this example it will be custom-line-item-blocked, which is important
    for the next section of this guide, for adding the snippets.
  • getLevel: The kind of error, available are notice, warning and error. Depending on that decision, the error will be printed in a blue,
    yellow or red box respectively. This example will use the error here.
  • blockOrder: Return a boolean on whether this exception should block the possibility to actually finish the checkout.
    In this case it will be true, hence the error level defined earlier. It wouldn't make sense to block the checkout, but only display a notice.
  • getParameters: You can add custom payload here. Technically any plugin or code could read the errors of the cart and act accordingly.
    If you need extra payload to your error class, this is the place to go.
So now let's have a look at the example error class:
<plugin root>/src/Core/Checkout/Cart/Custom/Error/CustomCartBlockedError.php
1
<?php declare(strict_types=1);
2
3
namespace Swag\BasicExample\Core\Checkout\Cart\Custom\Error;
4
5
use Shopware\Core\Checkout\Cart\Error\Error;
6
7
class CustomCartBlockedError extends Error
8
{
9
private const KEY = 'custom-line-item-blocked';
10
11
private string $lineItemId;
12
13
public function __construct(string $lineItemId)
14
{
15
$this->lineItemId = $lineItemId;
16
parent::__construct();
17
}
18
19
public function getId(): string
20
{
21
return $this->lineItemId;
22
}
23
24
public function getMessageKey(): string
25
{
26
return self::KEY;
27
}
28
29
public function getLevel(): int
30
{
31
// return self::LEVEL_NOTICE;
32
// return self::LEVEL_WARNING;
33
return self::LEVEL_ERROR;
34
}
35
36
public function blockOrder(): bool
37
{
38
return true;
39
}
40
41
public function getParameters(): array
42
{
43
return [ 'lineItemId' => $this->lineItemId ];
44
}
45
}
Copied!
The constructor was overridden so we can ask for the line item ID and save it in a property. Since we already used this class in the validator, we're basically done with that part here.
Only the snippets are missing.

Adding the snippet

First of all you should know our guide about adding storefront snippets, since that won't be explained in detail here.
You've defined the error key to be custom-line-item-blocked in your custom error class CustomCartBlockedError. Once your validator finds an invalid line item in your cart, Shopware is going to search for a respective snippet. In the cart, Shopware will be looking for the following snippet key: checkout.custom-line-item-blocked. Meanwhile it will be looking for a key error.custom-line-item-blocked in the checkout steps. This way you could technically define two different messages for the cart and the following checkout steps.
Now let's have a look at an example snippet file:
<plugin root>/src/Resources/snippet/en:GB/example.en-GB.json
1
{
2
"checkout": {
3
"custom-line-item-blocked": "Example error message for the cart"
4
},
5
"error": {
6
"custom-line-item-blocked": "Example error message for the checkout"
7
}
8
}
Copied!
This way Shopware will find the new snippets in your plugin and display the respective error message.
And that's it, you've now successfully added your own cart validator.

Next steps

In the examples mentioned above, we're asking for custom line item payloads. This subject is covered in our guide about adding cart items, so you might want to have a look at that.