aegisora/state-transition-rule

Aegisora validation rule for checking allowed state transitions in Aegisora ecosystem

Maintainers

Package info

github.com/Aegisora/state-transition-rule

Documentation

pkg:composer/aegisora/state-transition-rule

Transparency log

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-06-20 17:29 UTC

This package is auto-updated.

Last update: 2026-06-20 17:31:40 UTC


README

Code Coverage Badge Software License PHPStan Badge

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

  • ๐Ÿ”น 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 โ†’ Result validation flow
  • ๐Ÿ”น No raw booleans โ€” only structured results
  • ๐Ÿ”น Safe execution via base Rule abstraction
  • ๐Ÿ”น 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 StateTransition value via Context
  • checks whether transition from source state to target 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 โ†’ paid
  • draft โ†’ 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 โ€” source State
  • $to โ€” target State

StateTransitionMap::create($sourceState, $transitionStates);

  • $sourceState โ€” source State
  • $transitionStates โ€” list of allowed target State objects

StateTransitionMaps::create($maps);

  • $maps โ€” list of StateTransitionMap objects

StateTransitionMaps::createFromArray($rawData);

  • $rawData โ€” raw transition map array

StateTransitionRule::create($allowedTransitions);

  • $allowedTransitions โ€” StateTransitionMaps instance

๐Ÿ›๏ธ Architecture

This package relies on aegisora/rule-contract.

Flow:

  1. validate() is called
  2. Context is passed in
  3. StateTransition is extracted from context
  4. Source state is searched in allowed transition maps
  5. Target state is checked against allowed transition states
  6. Result is 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.