teknoo/states
Teknoo States provides a simple, extensible finite state machine implementation for PHP. Model objects with states and transitions, define allowed changes, and trigger callbacks on entry, exit, or transition events—useful for workflows, order processing, and status management.
Architecture fit
switch/if ladders. It carries no persistence, orchestration, or eventing concerns, so the blast radius is small but the scope is narrow: it solves exactly one problem — removing conditional sprawl from rich domain objects.Closure instances rebound to the stated class; inside the closure, $this/self/static refer to the proxy, not the state. This yields real PHP method semantics and visibility enforcement across states, but it is a frame-breaking idiom that defeats IDE navigation and is opaque to tooling unless the shipped PHPStan extension is loaded. Treat "team absorbs the idiom" as the dominant adoption cost.enum + match or symfony/workflow delivers most of the value without the idiom tax.Integration feasibility
teknoo/immutable), BSD-3-Clause, no daemon, no DB requirement, no Laravel service provider or config. Doctrine ORM/ODM support ships as an optional infrastructure layer (StandardTrait, AbstractStandardObject).^8.4. Environments on 8.1–8.3 must upgrade first — usually a multi-quarter platform program, not a library decision. This is the single largest feasibility filter.phpstan.neon does not load the extension. Adopting the idiom at scale means pinning PHPStan hard or accepting periodic red CI.Technical risk
Closure is not guessable. Correctness depends on developers remembering : Closure return types and closure/arrow-function returns. Mistakes fail late — at call time on the affected code path.debug_backtrace() (medium): builder visibility is enforced by walking the call stack. This can behave differently behind framework proxies (Doctrine lazy proxies, DI decorators, mocking tools) and adds per-call overhead.#[StateClass] attributes; attribute scanning plus RuntimeCache implies warm-up costs and cache-coherence questions under long-running workers (RoadRunner/Swoole/FrankenPHP) — must be measured.statesListDeclaration() → #[StateClass]) and a PHPUnit config that fails on any deprecation/notice. Good hygiene, moving target on bumps.Key questions
__serialize() builder and state serialization semantics need a spike.symfony/workflow or enum + match satisfy the requirement with less novelty? Answer in writing, not verbally.Stack fit
debug_backtrace() is measurable, and codebases where an internals-coupled PHPStan extension is a CI liability.composer require plus a namespace import.Migration path
StateMachine/HasStates contract) so domain classes are reachable only through our abstraction; a future swap to symfony/workflow becomes one adapter change.teknoo/states and phpstan/phpstan to exact versions and commit the application composer.lock (upstream's "lock is ignored" rule is library-authoring convention, not consumer guidance).statesListDeclaration() and convert to #[StateClass] in the same PR; the library emits E_USER_DEPRECATED and a strict PHPUnit config will fail the build.StateDefault auto-enable convention, and child-overloads-parent-by-short-class-name. Do not rely on upstream docs as the sole reference.Compatibility
declare(strict_types=1); and PSR-12 (120-char lines) — matches upstream, so no conflict.phpstan.neon rather than block a security update.Sequencing Platform upgrade to 8.4 → dependency pin + time-boxed spike (two sprints, one aggregate) → parity verification → docs and a review gate → a second aggregate with no new scope → decide on wider rollout at the quarter boundary. Never run two migrations of this kind concurrently; the idiom, not the tooling, is the bottleneck.
Maintenance
make target is clean depend — it rm -rf vendor and runs composer update; never call bare make in an automated job. Use make qa-offline where network is unavailable.Support
Scaling
debug_backtrace() visibility resolution, reflection/attribute scanning, RuntimeCache behavior) and coherence of cached state metadata under long-running workers. Benchmark before putting stated objects on a hot path; on long-running workers verify cache warm-up and invalidation behavior explicitly.Failure modes
: Closure return type surfaces at the call site on a specific state, not at deploy time — coverage of every state method on every state is therefore mandatory.StateException-style failures when Doctrine or DI proxies mediate calls.Ramp-up
#[StateClass]/StateDefault conventions are internalized. Budget two engineers for the initial spike and measure their onboarding time as a primary spike output.How can I help you explore Laravel packages today?