philiprehberger/php-state-machine

Declarative state machine with guards, hooks, and transition history

Maintainers

Package info

github.com/philiprehberger/php-state-machine

pkg:composer/philiprehberger/php-state-machine

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.3 2026-03-17 20:06 UTC

This package is auto-updated.

Last update: 2026-03-17 20:07:40 UTC


README

Tests Latest Version on Packagist License

Declarative state machine with guards, hooks, and transition history. Framework-agnostic, zero external dependencies.

Requirements

Dependency Version
PHP ^8.2

Installation

composer require philiprehberger/php-state-machine

Usage

Define a state machine

use PhilipRehberger\StateMachine\StateMachine;

$sm = StateMachine::define()
    ->states(['pending', 'processing', 'shipped', 'delivered', 'cancelled'])
    ->initial('pending')
    ->stateProperty('state')
    ->transition('process', 'pending', 'processing')
    ->transition('ship', 'processing', 'shipped')
    ->transition('deliver', 'shipped', 'delivered')
    ->transition('cancel', ['pending', 'processing'], 'cancelled')
    ->build();

Apply transitions

$order = new Order(); // $order->state === 'pending'

$result = $sm->apply($order, 'process');
// $order->state === 'processing'
// $result->from === 'pending'
// $result->to === 'processing'

Check if a transition is allowed

$sm->can($order, 'ship');    // true
$sm->can($order, 'deliver'); // false

Get allowed transitions

$sm->allowedTransitions($order); // ['ship', 'cancel']

Guards

Guards are callables that must return true for the transition to proceed:

$sm = StateMachine::define()
    ->states(['pending', 'processing', 'shipped'])
    ->initial('pending')
    ->transition('process', 'pending', 'processing')
        ->guard(fn (object $order) => $order->isPaid)
    ->transition('ship', 'processing', 'shipped')
    ->build();

Before and after hooks

$sm = StateMachine::define()
    ->states(['pending', 'processing'])
    ->initial('pending')
    ->transition('process', 'pending', 'processing')
        ->before(fn (object $order) => $order->log[] = 'Processing started')
        ->after(fn (object $order) => $order->log[] = 'Processing complete')
    ->build();

Transition history

$sm->apply($order, 'process');
$sm->apply($order, 'ship');

$history = $sm->history();
$history->all();  // [TransitionResult, TransitionResult]
$history->last(); // TransitionResult { transition: 'ship', from: 'processing', to: 'shipped' }

API

Method Description
StateMachine::define() Create a new StateMachineBuilder
$sm->apply(object $entity, string $transition) Apply a transition, returns TransitionResult
$sm->can(object $entity, string $transition) Check if a transition is allowed
$sm->allowedTransitions(object $entity) Get names of all allowed transitions
$sm->currentState(object $entity) Get the entity's current state
$sm->history() Get the TransitionHistory instance
$sm->initialState() Get the defined initial state
$sm->states() Get all defined states

StateMachineBuilder

Method Description
->states(array $states) Define valid states
->initial(string $state) Set the initial state
->stateProperty(string $property) Set the entity property name (default: 'state')
->transition(string $name, string|array $from, string $to) Define a transition
->build() Build the StateMachine

TransitionBuilder

Method Description
->guard(callable $guard) Add a guard (must return true to allow)
->before(callable $hook) Add a before-transition hook
->after(callable $hook) Add an after-transition hook

Development

composer install
vendor/bin/phpunit
vendor/bin/pint --test
vendor/bin/phpstan analyse

License

MIT