Pular para o conteúdo principal

Error Handling

By default, the state machine returns false or null when a transition is not possible. However, you can configure it to throw exceptions instead.

Enabling Exception Throwing​

Use the throwErrorIfCannotTransition() method to enable exception throwing:

$stateMachine = FiniteStateMachine::createMachine(Letter::class)
->addTransition($transitionA_B)
->addTransition($transitionA_C)
->throwErrorIfCannotTransition();

Exception Behavior​

With canTransition()​

When exceptions are enabled, canTransition() will throw a TransitionException if the transition is not possible:

try {
$stateMachine->canTransition(Letter::A, Letter::D);
} catch (TransitionException $e) {
echo $e->getMessage(); // "Cannot transition from A to D"
}

With transition()​

transition() returns null when the move is not declared or its condition denies it. With exceptions enabled it throws instead:

try {
$stateMachine->transition(Letter::B, Letter::A);
} catch (TransitionException $e) {
echo $e->getMessage(); // "Cannot transition from B to A"
}

With autoTransitionFrom()​

When exceptions are enabled, autoTransitionFrom() will throw a TransitionException if no valid transition is found:

try {
$stateMachine->autoTransitionFrom(Stock::Start, ["invalid" => "data"]);
} catch (TransitionException $e) {
echo $e->getMessage(); // "There is not possible transitions from __VOID__ with the data provided"
}

Ambiguous Transitions​

throwErrorIfCannotTransition() covers the case where no transition matches. The opposite case — data matching several transitions — is not an error by default: autoTransitionFrom() takes the first transition declared that matches.

Install Selector\RejectAmbiguous when your conditions are meant to be mutually exclusive and you would rather hear about an overlap than depend on declaration order:

use ByJG\StateMachine\Selector\RejectAmbiguous;

$stateMachine = FiniteStateMachine::createMachine(Stock::class)
->addTransition($transitionRequested)
->addTransition($transitionUnavailable)
->selectWith(new RejectAmbiguous()); // or the shorthand: throwErrorIfAmbiguousTransition()

try {
$stateMachine->autoTransitionFrom(Stock::LastUnits, ["invoice_number" => 10, "status" => "DNB"]);
} catch (TransitionException $e) {
// "Ambiguous transition from LAST_UNITS: the data provided matches REQUESTED_RESUPPLY, UNAVAILABLE"
echo $e->getMessage();
}

The two options are independent and can be combined: throwErrorIfCannotTransition() governs no match, the selector governs several. Rejecting the overlap is not the only way to settle it — Selector\HighestPriority ranks the moves instead of forbidding the tie. See Choosing the Winner.

Exception Class​

The TransitionException class extends the standard PHP Exception class:

use ByJG\StateMachine\TransitionException;

A State That Does Not Exist​

The errors above are answers: the machine is telling you a move it knows about is not allowed. Naming a state the machine does not have is a different thing — a mistake in the caller — and it always throws, whether or not throwErrorIfCannotTransition() is enabled:

$stateMachine->isFinalState('REVIEWD'); // TransitionException: not a state of this machine
$stateMachine->isFinalState(OtherEnum::Draft); // TransitionException: defined by Stock

This is what binding the machine to an enum buys. Without it, isFinalState('REVIEWD') would answer true — perfectly logically, since nothing leaves a state nobody declared.