SF
{ }
Symfony · Workflow Component · State Machine · PHP
Symfony Workflow Component:
State Machine für komplexe Prozesse

Bestellprozesse, Genehmigungsworkflows, Content-Publishing und Ticket-Systeme haben eines gemeinsam: Objekte wechseln ihren Zustand nach definierten Regeln. Wer diese Übergänge mit if-Konstrukten in Services implementiert, baut technische Schulden auf. Das Symfony Workflow Component macht State Machines zu deklarativer Konfiguration mit vollem Event-Support und Visualisierung.

18 Min. Lesezeit Zustände · Übergänge · Guards · Events · Visualisierung Symfony 7.x · PHP 8.3+

1. Warum das Symfony Workflow Component statt manueller if-Logik

Jedes PHP-Projekt, das Objekte mit Zuständen verwaltet – Bestellungen, Tickets, Dokumente, Benutzerkonten – entwickelt früher oder später ein Netz aus if ($order->getStatus() === 'pending')-Prüfungen, das quer durch Services und Controller verstreut ist. Wer darf den Übergang auslösen? Welche Zustände dürfen aufeinander folgen? Was passiert beim Übergang (E-Mail, Log, Notification)? Diese Fragen landen in Methoden wie confirmOrder(), cancelOrder() und shipOrder(), jede mit eigenen Zustandsprüfungen, die niemand vollständig im Kopf hat. Das Symfony Workflow Component löst dieses Problem fundamental: Die erlaubten Übergänge sind zentralisiert deklariert, der Code ruft nur noch $workflow->apply($order, 'confirm') auf und das Component prüft, ob der Übergang erlaubt ist.

Der Kerngewinn des Symfony Workflow Components ist Explizitheit. Die gesamte State Machine – alle Zustände, alle Übergänge, alle Bedingungen – steht in einer einzigen YAML-Datei (oder PHP-Konfiguration). Neue Entwickler im Team verstehen den Prozess durch Lesen der Konfiguration, nicht durch mühsames Durchsuchen von Services nach Statuschecks. Wenn ein neuer Übergang hinzukommt, wird er an einer Stelle hinzugefügt. Wenn ein Übergang entfernt werden soll, wird er dort entfernt – ohne Suche nach allen Stellen im Code, die diesen Übergang prüfen oder auslösen.

Ein weiterer entscheidender Vorteil: Das Symfony Workflow Component bietet eingebaute Visualisierung über Graphviz oder Mermaid. Mit bin/console workflow:dump bestellung | dot -Tpng -o bestellung.png entsteht ein automatisch generiertes Diagramm der State Machine, das immer synchron mit dem Code ist. Das ist ein enormer Vorteil in der Kommunikation mit nicht-technischen Stakeholdern: Der Business Analyst sieht dasselbe Diagramm, das der Entwickler implementiert hat – ohne manuelle Aktualisierung.

2. Kernkonzepte: Workflow, State Machine und der Unterschied

Das Symfony Workflow Component kennt zwei Typen: workflow und state_machine. Der Unterschied ist grundlegend: Eine State Machine erlaubt, dass ein Objekt zu jedem Zeitpunkt genau einen Zustand hat. Ein Workflow erlaubt mehrere gleichzeitige Zustände (sogenannte Marking) – wie ein Petrinetz-Modell. Für klassische Bestellprozesse, Ticket-Systeme und Content-Publishing-Workflows ist die State Machine das richtige Modell. Für parallele Prozesse, bei denen ein Dokument gleichzeitig "in Überprüfung" und "ausstehend für Zahlung" sein kann, ist der Workflow-Typ passender.

Die Kernbegriffe des Symfony Workflow Components: Ein Place ist ein Zustand (z.B. pending, paid, shipped). Eine Transition ist ein benannter Übergang zwischen Zuständen (z.B. pay wechselt von pending zu paid). Das Marking ist die aktuelle Position des Objekts im Workflow – bei State Machines ein einzelner Zustand, bei Workflows eine Menge von Places. Das Marking Store ist der Mechanismus, wie das Marking am Objekt gespeichert wird – standardmäßig als einzelne Eigenschaft bei State Machines oder als JSON-Array bei Workflows. Das Subjekt ist das PHP-Objekt (Entity, DTO), das durch den Workflow navigiert.

Die Wahl zwischen Workflow und State Machine im Symfony Component hat direkte Auswirkungen auf die Datenbankschicht: Bei State Machines ist eine einzelne String-Spalte für den aktuellen Status ausreichend. Bei Workflows muss das Marking als JSON oder in einer separaten Tabelle gespeichert werden. Für 95 % der typischen Geschäftsprozesse ist die State Machine der richtige Ansatz – einfacher zu modellieren, einfacher zu verstehen und einfacher in der Datenbank abzubilden.

3. Workflow deklarativ in YAML konfigurieren

Die Konfiguration eines Symfony Workflow Components in YAML ist das Herzstück des Ansatzes. Die Struktur ist klar: Unter framework.workflows oder framework.workflows definiert man für jeden Prozess einen Namen, die Klasse des Subjekts (supports), den Typ (type: state_machine), die Zustände (places), den Anfangszustand (initial_marking) und die Übergänge (transitions). Jeder Übergang hat einen Namen, eine oder mehrere Quell-Places (from) und eine oder mehrere Ziel-Places (to).

Das Symfony Workflow Component unterstützt auch PHP-basierte Konfiguration für Projekte, die YAML-freie Setups bevorzugen. Die PHP-Konfiguration in config/packages/workflow.php hat denselben Informationsgehalt, ist aber typsicher und refactorable. Für komplexe Workflows mit vielen Übergängen ist PHP-Konfiguration oft lesbarer als verschachteltes YAML. Symfony Flex legt bei der Installation des Components eine Beispiel-Konfigurationsdatei an, die als Ausgangspunkt dient.


<?php
// config/packages/workflow.yaml — Order state machine configuration
// framework:
//   workflows:
//     order_process:
//       type: state_machine
//       marking_store:
//         type: method
//         property: status   # calls $order->setStatus() / $order->getStatus()
//       supports:
//         - App\Entity\Order
//       initial_marking: pending
//       places:
//         - pending
//         - confirmed
//         - paid
//         - processing
//         - shipped
//         - delivered
//         - cancelled
//         - refunded
//       transitions:
//         confirm:
//           from: pending
//           to:   confirmed
//         pay:
//           from: confirmed
//           to:   paid
//         process:
//           from: paid
//           to:   processing
//         ship:
//           from: processing
//           to:   shipped
//         deliver:
//           from: shipped
//           to:   delivered
//         cancel:
//           from: [pending, confirmed]
//           to:   cancelled
//         refund:
//           from: [paid, processing, shipped, delivered]
//           to:   refunded

// PHP equivalent — same config, type-safe:
// return static function (FrameworkConfig $framework): void {
//     $workflow = $framework->workflows()->workflow('order_process');
//     $workflow->type('state_machine');
//     $workflow->supports([Order::class]);
//     $workflow->initialMarking(['pending']);
//     // ... places and transitions
// };

4. Entities und Objekte als Workflow-Subjekte

Das Objekt, das durch den Symfony Workflow navigiert, muss zwei Bedingungen erfüllen: Es muss einer der in supports deklarierten Klassen entsprechen, und der Marking Store muss auf seine Eigenschaft zugreifen können. Der Standard-Marking-Store des Symfony Workflow Components ist der Method-Store: Er ruft $object->setStatus($state) und $object->getStatus() auf – entsprechend einer normalen Doctrine-Entity mit einer Status-Spalte. Das Property-Store-Muster greift direkt auf eine öffentliche Eigenschaft zu, ohne Getter/Setter.

Für Doctrine-Entities empfiehlt sich ein Enum-Typ für die Status-Spalte statt eines einfachen Strings. Symfony Workflow arbeitet mit Strings als State-Identifikatoren, aber PHP 8.1 Backed Enums können leicht konvertiert werden: Die Getter/Setter-Methoden der Entity konvertieren zwischen dem Enum-Wert und dem String. Damit ist der Zustand in der Datenbank als Enum-Spalte mit Constraint gespeichert, und PHP-Code referenziert immer den Enum-Typ – kein magischer String-Vergleich mehr im Anwendungscode.

Wenn mehrere Workflows auf demselben Objekt-Typ laufen – etwa ein Bestell-Workflow und ein separater Rückgabe-Workflow für dieselbe Order-Entity – konfiguriert man mehrere Symfony Workflows mit verschiedenen Status-Eigenschaften. Die Order-Entity bekommt dann zwei Felder: $orderStatus für den Bestell-Workflow und $returnStatus für den Rückgabe-Workflow. Das Symfony Workflow Component lädt den passenden Workflow anhand der supports-Konfiguration, oder man gibt den Workflow-Namen explizit an, wenn mehrere Workflows dieselbe Klasse unterstützen.

5. Guards: bedingte Übergänge mit Sicherheitsprüfungen

Guards im Symfony Workflow Component sind Event-Listener, die auf das GuardEvent reagieren und entscheiden, ob ein Übergang aktuell erlaubt ist. Sie ergänzen die deklarative Übergangskonfiguration um dynamische Bedingungen, die von Objektzustand, Benutzerrechten oder externen Faktoren abhängen. Das Symfony Workflow-System feuert vor jedem Übergang ein Guard-Event, das der Listener blockieren kann. Wenn der Listener $event->setBlocked(true) aufruft, schlägt $workflow->can($order, 'ship') fehl und ein Aufruf von $workflow->apply($order, 'ship') wirft eine NotEnabledTransitionException.

Typische Guard-Anwendungsfälle im Symfony Workflow: Ein Versandübergang (ship) darf nur ausgeführt werden, wenn die Lieferadresse vollständig ausgefüllt ist. Ein Genehmigungsübergang (approve) ist nur für Benutzer mit der Rolle ROLE_MANAGER erlaubt. Ein Bezahlübergang ist nur möglich, wenn die Zahlung tatsächlich beim Payment-Provider als erfolgreich bestätigt wurde. Guards prüfen diese Bedingungen ohne Duplizierung: Dieselbe Guard-Klasse wird für alle Code-Stellen aktiv, die den Übergang auslösen – egal ob über Controller, CLI-Befehl oder automatisierten Hintergrundprozess.

Guards werden als Symfony-Services mit dem Event-Listener-Tag konfiguriert oder tragen das #[AsEventListener]-Attribut. Der Event-Name folgt dem Muster workflow.{workflow_name}.guard.{transition_name} für spezifische Guards oder workflow.{workflow_name}.guard für Guards, die bei allen Übergängen eines Workflows greifen. Das Attribut expression in der Workflow-YAML-Konfiguration ermöglicht auch einfache Guards direkt in der Konfiguration über die Symfony ExpressionLanguage, ohne eigene PHP-Klasse – für Bedingungen wie "is_granted('ROLE_ADMIN')".


<?php

declare(strict_types=1);

namespace App\Workflow\Guard;

use App\Entity\Order;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Workflow\Event\GuardEvent;

/**
 * Guard that blocks the 'ship' transition if the delivery address is incomplete.
 * Also blocks 'approve' transitions for non-admin users.
 */
final readonly class OrderWorkflowGuard
{
    public function __construct(
        private AuthorizationCheckerInterface $authorizationChecker,
    ) {}

    /**
     * Prevent shipping if the delivery address is missing required fields.
     */
    #[AsEventListener(event: 'workflow.order_process.guard.ship')]
    public function guardShipTransition(GuardEvent $event): void
    {
        /** @var Order $order */
        $order = $event->getSubject();

        if ($order->getDeliveryAddress() === null) {
            $event->setBlocked(true, 'Keine Lieferadresse hinterlegt.');
            return;
        }

        if (!$order->getDeliveryAddress()->isComplete()) {
            $event->setBlocked(true, 'Lieferadresse ist unvollständig.');
        }
    }

    /**
     * Only allow managers and admins to approve orders.
     */
    #[AsEventListener(event: 'workflow.order_process.guard.confirm')]
    public function guardConfirmTransition(GuardEvent $event): void
    {
        if (!$this->authorizationChecker->isGranted('ROLE_MANAGER')) {
            $event->setBlocked(true, 'Nur Manager können Bestellungen bestätigen.');
        }
    }

    /**
     * Block cancellation of orders that already have a shipment tracking number.
     */
    #[AsEventListener(event: 'workflow.order_process.guard.cancel')]
    public function guardCancelTransition(GuardEvent $event): void
    {
        /** @var Order $order */
        $order = $event->getSubject();

        if ($order->getTrackingNumber() !== null) {
            $event->setBlocked(true, 'Bereits versendete Bestellungen können nicht storniert werden.');
        }
    }
}

6. Workflow-Events für Nebeneffekte nutzen

Das Symfony Workflow Component feuert während jedes Übergangs mehrere Events, die für Nebeneffekte genutzt werden – E-Mails, Notifications, Audit-Logs, externe API-Calls und Cache-Invalidierung. Die Events folgen einem festen Lebenszyklus: workflow.guard (Erlaubnisprüfung), workflow.leave (Objekt verlässt Quell-State), workflow.transition (Übergang wird ausgeführt), workflow.enter (Objekt betritt Ziel-State), workflow.entered (Objekt ist im neuen State), workflow.completed (Übergang abgeschlossen), workflow.announce (aktivierbare Folgezustände ankündigen).

Für den Bestellprozess bedeutet das: Ein Event-Listener auf workflow.order_process.entered.paid sendet automatisch eine Zahlungsbestätigung per E-Mail, sobald die Bestellung den Zustand paid erreicht. Ein anderer Listener auf workflow.order_process.entered.shipped schickt die Tracking-Nummer per SMS. Diese Listener sind unabhängig voneinander, leicht zu testen und können deaktiviert werden, ohne den Kern des Symfony Workflows zu ändern. Das Publish-Subscribe-Muster hält Business-Logik und Nebeneffekte sauber getrennt.

Die Events sind nach dem Prinzip der höchsten Spezifität geordnet: workflow.order_process.entered.paid feuert nur für den spezifischen Workflow und den spezifischen Zielzustand. workflow.order_process.entered feuert für alle Zustandswechsel im Order-Workflow. workflow.entered feuert für alle Workflows aller Typen. Listener auf dem spezifischsten Event sind die bevorzugte Variante, weil sie explizit dokumentieren, welcher Workflow-Übergang den Effekt auslöst. Das macht das System selbst-dokumentierend und verhindert, dass Listener versehentlich auf Übergänge anderer Workflows reagieren.

7. Den Workflow-Service im Code verwenden

Der Symfony Workflow-Service wird per Dependency Injection in Controller, Services oder Command-Handler injiziert. Symfony registriert für jeden konfigurierten Workflow automatisch einen Service unter dem Namen workflow.{name} – also workflow.order_process für den Beispiel-Workflow. Mit dem Interface WorkflowInterface oder dem abstrakten Typ Workflow im Konstruktor und dem #[Target]-Attribut (Symfony 7) wählt man den spezifischen Workflow per Named-Autowiring aus.

Die wichtigsten Methoden des Symfony Workflow-Services: $workflow->can($order, 'ship') prüft ob der Übergang erlaubt ist (Guards eingeschlossen), ohne ihn auszuführen. $workflow->apply($order, 'ship') führt den Übergang aus, feuert alle Events und wirft eine Exception wenn er nicht erlaubt ist. $workflow->getEnabledTransitions($order) gibt alle aktuell erlaubten Übergänge zurück – nützlich für UI-Elemente, die nur erlaubte Aktionen anzeigen sollen. $workflow->getMarking($order) gibt den aktuellen Zustand zurück.


<?php

declare(strict_types=1);

namespace App\Service;

use App\Entity\Order;
use Symfony\Component\Workflow\Exception\NotEnabledTransitionException;
use Symfony\Component\Workflow\WorkflowInterface;
use Symfony\Component\DependencyInjection\Attribute\Target;

/**
 * Service for managing order state transitions via the Symfony Workflow Component.
 */
final readonly class OrderWorkflowService
{
    public function __construct(
        // Named autowiring — selects the 'order_process' workflow specifically
        #[Target('order_process')]
        private WorkflowInterface $workflow,
    ) {}

    /**
     * Confirm the order if the transition is currently allowed.
     *
     * @throws NotEnabledTransitionException if the 'confirm' transition is blocked by a guard
     */
    public function confirmOrder(Order $order): void
    {
        // Explicit check — provides a clear error message before attempting
        if (!$this->workflow->can($order, 'confirm')) {
            $blockedTransitions = $this->workflow->buildTransitionBlockerList($order, 'confirm');
            throw new \DomainException(
                'Bestellung kann nicht bestätigt werden: ' .
                implode(', ', array_map(fn($b) => $b->getMessage(), iterator_to_array($blockedTransitions)))
            );
        }

        // Apply triggers guard, leave, transition, enter, entered, completed events
        $this->workflow->apply($order, 'confirm');
    }

    /**
     * Get all currently enabled transitions for the given order.
     * Use this to build dynamic action menus in UI components.
     *
     * @return list<string> Names of enabled transitions
     */
    public function getAvailableActions(Order $order): array
    {
        return array_map(
            fn($transition) => $transition->getName(),
            $this->workflow->getEnabledTransitions($order)
        );
    }

    /**
     * Check if the order is in a specific state.
     */
    public function isInState(Order $order, string $place): bool
    {
        return $this->workflow->getMarking($order)->has($place);
    }
}

8. Workflows testen: Unit- und Integrationstests

Das Symfony Workflow Component ist gut testbar auf mehreren Ebenen. Unit-Tests für Guards: Den Guard-Service direkt instanziieren, ein mock GuardEvent übergeben und prüfen, ob setBlocked(true) aufgerufen wurde. Unit-Tests für Event-Listener: Den Listener instanziieren, ein Mock-Event übergeben und prüfen, ob der erwartete Nebeneffekt (E-Mail-Versand, Datenbankoperationen) ausgelöst wurde. Diese Tests brauchen kein Symfony-Framework-Bootstrap.

Integrationstests für den vollständigen Symfony Workflow: Mit dem Symfony-Kernel und dem WorkflowInterface aus dem Container wird ein Order-Objekt durch den gesamten Workflow navigiert. Man prüft, welche Transitionen nach jedem Schritt erlaubt sind, ob Guards korrekt blockieren und ob Events korrekt gefeuert werden. Die Methode $workflow->getEnabledTransitions($order) liefert als Liste die erlaubten Übergänge, die mit assertCount und assertSame geprüft werden können.

Ein wichtiger Testfall: Ungültige Übergänge. $this->expectException(NotEnabledTransitionException::class); $workflow->apply($order, 'ship'); stellt sicher, dass ein Übergang, der nicht erlaubt ist (weil der Zustand falsch ist oder ein Guard blockiert), tatsächlich eine Exception wirft – nicht stillschweigend ignoriert wird. Das Symfony Workflow Component wirft immer eine Exception bei ungültigem Übergang, was die Testbarkeit erheblich vereinfacht im Vergleich zu manueller if-Logik, die oft keinen Fehler signalisiert.

9. Vergleich: Workflow vs. State Machine vs. manuelle Logik

Der Vergleich der drei Ansätze zeigt, warum das Symfony Workflow Component für komplexe Prozesse die überlegene Wahl ist und wann einfachere Ansätze sinnvoller sind.

Kriterium Manuelle if-Logik Symfony State Machine Symfony Workflow
Zustandsüberblick Im Code verstreut Zentrale YAML-Konfiguration Zentrale YAML-Konfiguration
Gleichzeitige Zustände Komplex zu verwalten Nicht unterstützt Nativ unterstützt
Visualisierung Manuell, immer veraltet Automatisch via Graphviz Automatisch via Graphviz
Testbarkeit Logik überall verteilt Guards und Listener isolierbar Guards und Listener isolierbar
Setup-Aufwand Keiner YAML + Marking Store YAML + Marking Store + JSON

Für einfache Objekte mit zwei oder drei Zuständen und klaren, unveränderlichen Übergängen ist das Symfony Workflow Component Overhead. Ein Artikel mit den Zuständen draft und published und einem einzelnen Übergang braucht kein Component – ein einfacher Boolean reicht. Ab vier oder mehr Zuständen, mehreren möglichen Übergängen und dynamischen Guard-Bedingungen rentiert sich das Symfony Workflow Component messbar: Die Konfiguration ist nach einer Woche noch lesbar, die Testabdeckung ist vollständig, und die Visualisierung ist immer aktuell.

Mironsoft

Symfony-Entwicklung, Workflow-Architektur und Prozessmodellierung

Komplexe Geschäftsprozesse in Symfony modellieren?

Wir modellieren Geschäftsprozesse als State Machines mit dem Symfony Workflow Component – von der Bestellabwicklung über Genehmigungsworkflows bis zu Content-Publishing-Prozessen mit vollständiger Event-Integration und Testabdeckung.

Workflow-Design

Zustände, Übergänge und Guards für eure Geschäftsprozesse modellieren und als Symfony-Konfiguration umsetzen

Event-Integration

Nebeneffekte wie E-Mails, Notifications und Audit-Logs über Workflow-Events entkoppelt implementieren

Migration

Bestehende if-Logik und Status-Enums in saubere Symfony Workflow State Machines refaktorieren

10. Zusammenfassung

Das Symfony Workflow Component ist die richtige Wahl, sobald ein Objekt mehr als drei Zustände hat oder Übergänge von dynamischen Bedingungen abhängen. Die deklarative YAML-Konfiguration macht den Prozess für das gesamte Team lesbar, die automatische Visualisierung via Graphviz ist immer synchron mit dem Code, und das Event-System ermöglicht Nebeneffekte ohne Kopplung an die Kern-Transition-Logik. Guards halten Berechtigungs- und Validierungslogik sauber von der Workflow-Konfiguration getrennt.

Der Einstieg ist denkbar einfach: Einen Workflow in config/packages/workflow.yaml definieren, das Symfony-Marking-Store an die Entity-Eigenschaft binden und den Workflow-Service per Constructor Injection in Services injizieren. Guards und Event-Listener kommen als normale Symfony-Services hinzu. Testing ist auf allen Ebenen isoliert möglich: Unit-Tests für Guards und Listener, Integrationstests für den vollständigen Workflow-Durchlauf. Das Symfony Workflow Component ersetzt fragile if-Konstrukte durch nachvollziehbare, testbare und visualisierbare State Machines.

Symfony Workflow Component — Das Wichtigste auf einen Blick

Konfiguration

Places, Transitions und Marking Store in YAML oder PHP deklarativ. type: state_machine für exklusiven Zustand, type: workflow für parallele Zustände.

Guards

Event-Listener auf workflow.{name}.guard.{transition} blockieren Übergänge dynamisch. Berechtigungsprüfung und Validierung ohne Kopplung an die Konfiguration.

Events

workflow.entered.{place}, workflow.completed etc. für Nebeneffekte. E-Mails, Notifications und Audit-Logs ohne Kopplung an die Transition-Logik.

Visualisierung

bin/console workflow:dump {name} | dot -Tpng -o graph.png erzeugt automatisch ein Diagramm – immer synchron mit der YAML-Konfiguration.

11. FAQ: Symfony Workflow Component und State Machines

1Was ist das Symfony Workflow Component?
Deklarative State Machines und Workflows in YAML/PHP. Objekte navigieren durch Zustände via Transitionen. Guards für Bedingungen, Events für Nebeneffekte, Graphviz für Visualisierung.
2Workflow vs. State Machine?
State Machine: genau ein Zustand gleichzeitig. Workflow: mehrere gleichzeitige Zustände (Petrinetz). Für Bestellprozesse ist state_machine die richtige Wahl.
3Übergänge in YAML definieren?
framework.workflows.{name}.transitions — Name, from (Quell-State) und to (Ziel-State). Zentrale Konfiguration, gilt für alle Code-Stellen die den Übergang ausführen.
4Was sind Guards?
Event-Listener auf GuardEvent – blockieren Übergänge dynamisch. setBlocked(true) lässt can() false zurückgeben und apply() eine NotEnabledTransitionException werfen.
5Wie wird der Zustand gespeichert?
Marking Store vom Typ method ruft getStatus()/setStatus() auf. Bei State Machines reicht eine String-Spalte in der Datenbank. Property direkt in marking_store konfigurieren.
6Workflow-Service injizieren?
#[Target('order_process')] mit WorkflowInterface im Konstruktor. Named Autowiring wählt den spezifischen Workflow. Methoden: can(), apply(), getEnabledTransitions(), getMarking().
7Workflow visualisieren?
bin/console workflow:dump {name} | dot -Tpng -o graph.png — immer synchron mit der Konfiguration. Kein manuelles Diagramm-Update nötig.
8Wann Workflow Component nutzen?
Ab vier oder mehr Zuständen, mehreren Übergängen oder dynamischen Guards. Für Zwei-Zustand-Objekte reicht ein Boolean. Break-Even: Prozesse, die regelmäßig erklärt oder geändert werden.
9Mehrere Workflows auf einer Entity?
Ja – mit unterschiedlichen Marking-Store-Properties. order_process nutzt $orderStatus, return_process nutzt $returnStatus – beide auf Order-Entity ohne Konflikte.
10Wie teste ich Workflow-Transitionen?
Unit: Guards und Listener mit Mock-Events. Integration: Workflow aus Container, Objekt durch Transitionen führen, getEnabledTransitions() prüfen. Ungültige Übergänge mit expectException(NotEnabledTransitionException::class).