aegisora / state-transition-rule
Aegisora validation rule for checking allowed state transitions in Aegisora ecosystem
Requires
- php: >=7.4
- aegisora/rule-contract: ^1.0
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^9.6
- squizlabs/php_codesniffer: ^4.0
README
State Transition Rule provides a simple, rule-based state transition validation implementation for the Aegisora ecosystem.
It is built on top of aegisora/rule-contract and follows its strict validation architecture, ensuring consistent and predictable behavior across applications.
This rule is useful for validating workflows, status changes, lifecycle transitions, and domain state machines.
๐ Table of Contents
- Features
- Installation
- Core Concept
- Basic Usage
- Array-Based Configuration
- Raw Data Normalization
- Models
- Validation Result
- Guardian Usage
- Real-World Examples
- Factory Methods
- Architecture
- License
- Contributing
- Support
โจ Features
- ๐น Lightweight and dependency-free except aegisora/rule-contract
- ๐น Validates transitions between named states
- ๐น Supports explicit allowed transition maps
- ๐น Supports array-based transition map creation
- ๐น Ignores invalid raw map data safely
- ๐น Deduplicates source states and transition states
- ๐น Fully compatible with Aegisora validation pipeline
- ๐น Strict
ContextโResultvalidation flow - ๐น No raw booleans โ only structured results
- ๐น Safe execution via base
Ruleabstraction - ๐น Simple factory API (create)
- ๐น Ready to use out of the box
๐ฆ Installation
composer require aegisora/state-transition-rule
๐ Core Concept
This package implements a single validation rule:
- accepts a
StateTransitionvalue viaContext - checks whether transition
fromsource statetotarget state is allowed - returns a standardized
Result
A transition is represented by two states:
from โ to
Example:
draft โ paid
The rule validates this transition against configured allowed transition maps.
๐๏ธ Basic Usage
use Aegisora\RuleContract\Models\Context; use Aegisora\Rules\StateTransition\Models\State; use Aegisora\Rules\StateTransition\Models\StateTransition; use Aegisora\Rules\StateTransition\Models\StateTransitionMap; use Aegisora\Rules\StateTransition\Models\StateTransitionMaps; use Aegisora\Rules\StateTransition\StateTransitionRule; $allowedTransitions = StateTransitionMaps::create([ StateTransitionMap::create(State::create('draft'), [ State::create('paid'), State::create('cancelled'),]), StateTransitionMap::create( State::create('paid'), [ State::create('shipped'), State::create('refunded'),]), ]); $transition = StateTransition::create(State::create('draft'), State::create('paid')); $result = StateTransitionRule::create($allowedTransitions)->validate(Context::create($transition)); if ($result->isValid()) { // transition is allowed } else { // transition is not allowed }
๐งฉ Array-Based Configuration
Allowed transitions may be created from raw array data using StateTransitionMaps::createFromArray().
use Aegisora\RuleContract\Models\Context; use Aegisora\Rules\StateTransition\Models\State; use Aegisora\Rules\StateTransition\Models\StateTransition; use Aegisora\Rules\StateTransition\Models\StateTransitionMaps; use Aegisora\Rules\StateTransition\StateTransitionRule; $allowedTransitions = StateTransitionMaps::createFromArray([ [ 'draft' => [ 'paid', 'cancelled', ],], [ 'paid' => [ 'shipped', 'refunded', ],], [ 'shipped' => [ 'completed',],], ]); $transition = StateTransition::create(State::create('paid'), State::create('shipped')); $result = StateTransitionRule::create($allowedTransitions)->validate(Context::create($transition)); if ($result->isValid()) { // paid โ shipped is allowed }
๐งน Raw Data Normalization
StateTransitionMaps::createFromArray() safely normalizes raw transition data.
It accepts only:
- non-empty string source state names
- array transition state lists
- non-empty string transition state names
Invalid values are ignored.
Duplicate source states are skipped after the first valid occurrence.
Duplicate transition states are also skipped.
Example:
$allowedTransitions = StateTransitionMaps::createFromArray([ ['draft' => ['paid', 'paid', '', 123, true, 'cancelled',],], ]);
The normalized map will contain:
draft โ paiddraft โ cancelled
๐งฑ Models
State
Represents a named state.
State::create('draft');
StateTransition
Represents transition from one state to another.
StateTransition::create(State::create('draft'), State::create('paid'));
StateTransitionMap
Represents allowed target states for a single source state.
StateTransitionMap::create( State::create('draft'), [State::create('paid'), State::create('cancelled'),] );
StateTransitionMaps
Represents a collection of transition maps.
StateTransitionMaps::create([ StateTransitionMap::create(State::create('draft'), [State::create('paid'),]), ]);
๐งช Validation Result
If transition is allowed, the rule returns a valid result.
$result->isValid(); // true
If transition is not allowed, the rule returns an invalid result.
$result->isValid(); // false $result->getFailedRuleCode(); // state_transition_rule
If the context value is not an instance of StateTransition, the rule throws:
Aegisora\RuleContract\Exceptions\InvalidRuleContextException
๐ Guardian Usage
This rule can be used together with aegisora/guardian to build fluent validation pipelines.
use Aegisora\Guardian\Guardian; use Aegisora\Rules\StateTransition\Models\State; use Aegisora\Rules\StateTransition\Models\StateTransition; use Aegisora\Rules\StateTransition\Models\StateTransitionMaps; use Aegisora\Rules\StateTransition\StateTransitionRule; use App\Exceptions\InvalidOrderStatusTransitionException; $guardian = new Guardian(); $allowedTransitions = StateTransitionMaps::createFromArray([ ['draft' => ['paid', 'cancelled',],], ['paid' => ['shipped', 'refunded',],], ]); $guardian ->that(StateTransition::create(State::create('draft'), State::create('paid'))) ->must(StateTransitionRule::create($allowedTransitions), new InvalidOrderStatusTransitionException()) ->validate();
If the transition is invalid, Guardian throws the provided domain exception.
๐งญ Real-World Examples
State Transition Rule is useful for validating domain workflows.
Examples
Order:
draft โ paid
paid โ shipped
shipped โ completed
Payment:
pending โ approved
pending โ declined
approved โ refunded
Ticket:
open โ in_progress
in_progress โ resolved
resolved โ closed
Publication:
draft โ review
review โ published
published โ archived
๐งฉ Factory Methods
State::create($name);
$nameโ state name
StateTransition::create($from, $to);
$fromโ sourceState$toโ targetState
StateTransitionMap::create($sourceState, $transitionStates);
$sourceStateโ sourceState$transitionStatesโ list of allowed targetStateobjects
StateTransitionMaps::create($maps);
$mapsโ list ofStateTransitionMapobjects
StateTransitionMaps::createFromArray($rawData);
$rawDataโ raw transition map array
StateTransitionRule::create($allowedTransitions);
$allowedTransitionsโStateTransitionMapsinstance
๐๏ธ Architecture
This package relies on aegisora/rule-contract.
Flow:
validate()is calledContextis passed inStateTransitionis extracted from context- Source state is searched in allowed transition maps
- Target state is checked against allowed transition states
Resultis returned
All logic is safely handled by Rule contract.
โ๏ธ License
This package is open-source and licensed under the MIT License. See the LICENSE for details.
๐ฑ Contributing
Contributions are welcome and greatly appreciated! See the CONTRIBUTING for details.
๐ Support
If you find this project useful, please consider giving it a star on GitHub!
It helps the project grow and motivates further development.