CartService/OrderService dekorieren oder der Validation mehr Bedingungen geben

Hallo, ich muss in einem Plugin, vor dem Abschluss der Bestellung und dementsprechend auch vor dem Speichern eine Prüfung einbauen, um gegebenenfalls einen Fehler zu werfen und die Bestellung nicht auszulösen.

Meine erste Idee war den Cart oder OrderService zu dekorieren, leider scheint das nicht zu klappen.

namespace MNS\Service;

class OrderServiceDecorator
{
    private $decoratedService;

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

    public function createOrder($data, $context)
    {

    }
}

gibt den Fehler:
Circular reference detected for service „MNS\Service\OrderServiceDecorator“, path: „MNS\Service\OrderServiceDecorator -> MNS\Service\OrderServiceDecorator“.
Gleiches passiert bei dem CartService. Ich kann hier im Constructor auch kein Interface angeben, da beide kein Interface besitzen.

Geht das prinzipiell, ist das angedacht von Shopware6, wenn ja was mache ich falsch?

Meine andere Idee war nicht den Service zu dekorieren sondern die ValidationFactory zu erweitern, Shopware\Core\Checkout\Order\Validation\OrderValidationFactory. Hier weiß ich allerdings gar nicht wie ich ran komme, gibt es hierfür möglichkeiten diese zu erweitern?

 

https://symfony.com/doc/current/service_container/service_decoration.html

.inner Fehlt :slight_smile:

Ok, ich bin ein Stück weiter

bringt mich schonmal an einen anderen Fehler 
Argument 5 passed to Shopware\Storefront\Controller\CheckoutController::__construct() must be an instance of Shopware\Core\Checkout\Order\SalesChannel\OrderService …
Auch Logisch, da ich kein Interface habe, kann ich das natürlich nicht mit übergeben.

Deshalb extende ich OrderService und rufe dann die createOrder über den decorated Service auf, das sollte evtl. funktionieren.

Einfacher und auch Eleganter wären hier natürlich Interfaces oder das Erweitern der Validierungsregeln.

Muss das denn tatsächlich nach dem Abschicken passieren?

Sonst könntest du dir mal folgendes anschauen:

 (so hab ich "mind. Bestellwert 100€" umgesetzt).

Irgendwo hatte ich auch mal gelesen, dass das jemand über shopware.cart.processor gemacht hat.

https://docs.shopware.com/en/shopware-platform-dev-en/how-to/indepth-guide-bundle/checkout

(edit: der Code wurde verschluckt)

Glaub du willst einen Cart Validator bauen

Beispiel: https://github.com/shopwareDowntown/downtown/blob/master/src/Voucher/Service/CartValidator.php

sobald du einen Error reinwirfst, kann die Bestellung nicht abgeschlossen werden

@Shyim‍ Danke das hat viele Probleme gelöst - konnte ihn an mehreren Stellen schon gut einsetzen.

Der CartValidator funktioniert bei allen Routes im Checkout, bis auf ‘frontend.checkout.finish.order’, wenn ich in dieser Route eine exception werfe, bekomme ich eine Symfony Fehlermeldung “The cart is invalid, got 1 error(s).”, werde aber nicht wieder zurück an auf die confirm seite geworfen bzw. kann es von dort aus ja auch nicht provozieren. 

Ich habe versucht mit dem “BuildValidationEvent” eine Fehlermeldung zu provozieren, irgendwie will das aber alles nicht richtig, nichtmal ein “die()” greift hier.

class ValidationSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            BuildValidationEvent::class => 'cartValidation'
        ];
    }

    public function cartValidation(BuildValidationEvent $event)
    {
        die('validation event');
    }
}

Ich muss nach der “confirm”-Seite prüfen ob alle Daten richtig sind und notfalls den User zurück zur “confirm”-Seite schicken, um ihm eine Fehlermeldung auszugeben. 

@gezeichnet schrieb:

@Shyim‍ Danke das hat viele Probleme gelöst - konnte ihn an mehreren Stellen schon gut einsetzen.

Der CartValidator funktioniert bei allen Routes im Checkout, bis auf ‚frontend.checkout.finish.order‘, wenn ich in dieser Route eine exception werfe, bekomme ich eine Symfony Fehlermeldung „The cart is invalid, got 1 error(s).“, werde aber nicht wieder zurück an auf die confirm seite geworfen bzw. kann es von dort aus ja auch nicht provozieren. 

Ich habe versucht mit dem „BuildValidationEvent“ eine Fehlermeldung zu provozieren, irgendwie will das aber alles nicht richtig, nichtmal ein „die()“ greift hier.

class ValidationSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
BuildValidationEvent::class => ‚cartValidation‘
];
}

public function cartValidation(BuildValidationEvent $event)
{
die(‚validation event‘);
}
}

Ich muss nach der „confirm“-Seite prüfen ob alle Daten richtig sind und notfalls den User zurück zur „confirm“-Seite schicken, um ihm eine Fehlermeldung auszugeben. 

Grossartige Fragen. Wir stehen gerade vor dem genau gleichen Problem. Dieser Thread war sehr hilfreich.

Gibt es bereits neue Erkenntnisse, wie die Bestellung nach „confirm“ überprüft/abgelehnt werden kann?

Ich bin vorsichtig geworden mit dem Cartvalidator, die Prüfung findet auch statt wenn du zu confirm gehst, wirft dort aber nur einen symfony Fehler, ohne das du eingreifen kannst. 

Ich hänge mich an das 

 framework.validation.order.create Event, dort kriegst du ein Buildvalidationevent. Mit dem du die Validierung der cart übernehmen kannst.

 

$data = $this->requestStack->getCurrentRequest()->request;
        // eigener validator, andere können im Core gefunden werden
        $definition = new DataValidationDefinition('XXX');
        $definition->add('name des validator',
            new ValidatorClass()
        );

        $violations = $this->validator->getViolations($data->all(), $definition);
        if (!$violations->count()) {
            return;
        }
        // wirf diese Exception und du kommst zurück zur confirm seite
        throw new ConstraintViolationException($violations, $data->all());

 

Sorry für evtl. Formatierungsfehler, bin am Smartphone unterwegs.

1 „Gefällt mir“

Hi @gezeichnet‍

kannst du hier nochmal etwas mehr Kontext geben? Ich habe genau das gleiche Problem wie du hier, weiß aber nicht was du mit “an das framework.validation.order.create Event hängen” meinst.

Kannst du mir erklären, wie du das gemacht hast? :slight_smile:

Ok, es war tatsächlich so einfach, dass ich es gar nicht erst so versucht habe:

public static function getSubscribedEvents(): array
    {
        return [
            'framework.validation.order.create' => 'onValidateOrder'
        ];
    }

 

1 „Gefällt mir“

Ich möchte hierzu kurz eine Info geben.
In dem BuildValidationEvent ist der Name „dynamisch“.
Dieser wird aus einem Prefix und dem Namen der in dem Event enthaltenen DataValidationDefinition gebildet, sodass z.b. folgender Name am ende herauskommt:

framework.validation.cms_extensions.form.create

Da muss man also in der getSubscribedEvents Methode nicht den Klassennamen als Key eintragen sondern genau diesen dynamischen Namen.

Dieser muss dann entsprechend im Code erst herausgefunden werden.

Ich denke das wurde so gemacht, damit man nicht auf alle BuildValidationEvents subscribed, da dieses Event doch an einigen Stellen genutzt wird. Stattdessen will man ja meist nur eine bestimmte Validation verändern.