PHPackages                             aegisora/state-transition-rule - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. aegisora/state-transition-rule

ActiveLibrary[Validation &amp; Sanitization](/categories/validation)

aegisora/state-transition-rule
==============================

Aegisora validation rule for checking allowed state transitions in Aegisora ecosystem

v1.0.0(today)00MITPHPPHP &gt;=7.4

Since Jun 20Pushed todayCompare

[ Source](https://github.com/Aegisora/state-transition-rule)[ Packagist](https://packagist.org/packages/aegisora/state-transition-rule)[ Docs](https://github.com/Aegisora/state-transition-rule)[ RSS](/packages/aegisora-state-transition-rule/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (4)Versions (2)Used By (0)

Aegisora State Transition Rule
==============================

[](#aegisora-state-transition-rule)

[![Code Coverage Badge](./badge.svg)](./badge.svg)[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](LICENSE)[![PHPStan Badge](https://camo.githubusercontent.com/83dd3d35cebed0eab9ee97ff1a5849c1344cda6a8ee9cac2cda20f5aa55b67bd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e7376673f7374796c653d666c6174)](https://camo.githubusercontent.com/83dd3d35cebed0eab9ee97ff1a5849c1344cda6a8ee9cac2cda20f5aa55b67bd/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e7376673f7374796c653d666c6174)

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`](https://github.com/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
-------------------

[](#-table-of-contents)

- [Features](#-features)
- [Installation](#-installation)
- [Core Concept](#-core-concept)
- [Basic Usage](#-basic-usage)
- [Array-Based Configuration](#-array-based-configuration)
- [Raw Data Normalization](#-raw-data-normalization)
- [Models](#-models)
- [Validation Result](#-validation-result)
- [Guardian Usage](#-guardian-usage)
- [Real-World Examples](#-real-world-examples)
- [Factory Methods](#-factory-methods)
- [Architecture](#-architecture)
- [License](#-license)
- [Contributing](#-contributing)
- [Support](#-support)

---

✨ Features
----------

[](#-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
--------------

[](#-installation)

```
composer require aegisora/state-transition-rule
```

---

🚀 Core Concept
--------------

[](#-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
--------------

[](#️-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
---------------------------

[](#-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
------------------------

[](#-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
--------

[](#-models)

### `State`

[](#state)

Represents a named state.

`State::create('draft');`

### `StateTransition`

[](#statetransition)

Represents transition from one state to another.

```
StateTransition::create(State::create('draft'), State::create('paid'));
```

### `StateTransitionMap`

[](#statetransitionmap)

Represents allowed target states for a single source state.

```
StateTransitionMap::create(
    State::create('draft'),
    [State::create('paid'), State::create('cancelled'),]
);
```

### `StateTransitionMaps`

[](#statetransitionmaps)

Represents a collection of transition maps.

```
StateTransitionMaps::create([
    StateTransitionMap::create(State::create('draft'), [State::create('paid'),]),
]);
```

---

🧪 Validation Result
-------------------

[](#-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
----------------

[](#-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
---------------------

[](#-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
-----------------

[](#-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
---------------

[](#️-architecture)

This package relies on [`aegisora/rule-contract`](https://github.com/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
----------

[](#️-license)

This package is open-source and licensed under the MIT License. See the [LICENSE](LICENSE) for details.

---

🌱 Contributing
--------------

[](#-contributing)

Contributions are welcome and greatly appreciated! See the [CONTRIBUTING](CONTRIBUTING.md) for details.

---

🌟 Support
---------

[](#-support)

If you find this project useful, please consider giving it a star on GitHub!

It helps the project grow and motivates further development.

###  Health Score

36

—

LowBetter than 80% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 77.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/81402dcd0a07ad550b7f80f5871e7c302770b29d4c73a52fc35ba697f702d56e?d=identicon)[arslanim](/maintainers/arslanim)

---

Top Contributors

[![arslanim](https://avatars.githubusercontent.com/u/22678154?v=4)](https://github.com/arslanim "arslanim (183 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (53 commits)")

---

Tags

aegisoraaegisora-ecosystemphprulestatestate-machinestate-transitionvalidationvalidatorworkflowphpvalidatorvalidationstateworkflowrulestate-machinestate transitionaegisoraaegisora-ecosystem

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP\_CodeSniffer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/aegisora-state-transition-rule/health.svg)

```
[![Health](https://phpackages.com/badges/aegisora-state-transition-rule/health.svg)](https://phpackages.com/packages/aegisora-state-transition-rule)
```

###  Alternatives

[illuminatech/validation-composite

Allows uniting several validation rules into a single one for easy re-usage

180517.4k](/packages/illuminatech-validation-composite)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
