From e41d0cb32f84eee1711bd64e08504d06ba4752bd Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 10:56:21 -0500 Subject: [PATCH 1/6] Introduce StateContext --- examples/ContextUsingBuilderTest.php | 24 ++++---- examples/ContextUsingCustomMetadataTest.php | 13 ++-- src/Builder/StateBuilder.php | 9 ++- src/Callbacks/TransitionCallback.php | 23 +++++-- src/Event/Adapter/ObjectAdapterContext.php | 38 ++++++++++++ src/Event/Adapter/StringAdapterContext.php | 33 ++++++++++ src/Event/TransitionWasFailed.php | 31 +++++++--- src/Event/TransitionWasRequested.php | 27 +++++++-- src/Event/TransitionWasSuccessful.php | 27 +++++++-- src/InvalidStateTransitionException.php | 20 ++++-- src/StateContext.php | 17 ++++++ src/StateMachine.php | 43 ++++++++++--- tests/Context/TestStubContext.php | 18 ++++++ tests/StateMachineTest.php | 67 ++++++++++++++++++++- tests/TestContext.php | 4 ++ 15 files changed, 339 insertions(+), 55 deletions(-) create mode 100644 src/Event/Adapter/ObjectAdapterContext.php create mode 100644 src/Event/Adapter/StringAdapterContext.php create mode 100644 src/StateContext.php create mode 100644 tests/Context/TestStubContext.php diff --git a/examples/ContextUsingBuilderTest.php b/examples/ContextUsingBuilderTest.php index 848ebbc..ed26c25 100644 --- a/examples/ContextUsingBuilderTest.php +++ b/examples/ContextUsingBuilderTest.php @@ -10,6 +10,7 @@ use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; final class ContextUsingBuilderTest extends TestCase @@ -192,7 +193,7 @@ public function test_it_should_allow_to_define_attributes_on_state(): void /** * Example of usage when using self contained workflow creation. */ -final class Post +final class Post implements StateContext { const ALIAS = 'post'; @@ -207,16 +208,18 @@ final class Post const ATTRIBUTE_ACTIVE = 'active'; const ATTRIBUTE_CLOSED = 'closed'; - /** - * @var string - */ - private $state; + private string $state; private function __construct(string $state) { $this->state = $state; } + public function toStateContextIdentifier(): string + { + return self::ALIAS; + } + public function isDraft(): bool { return $this->workflow()->isInState(self::STATE_DRAFT); @@ -244,23 +247,20 @@ public function isClosed(): bool public function moveToDraft(): void { - $this->state = $this->workflow()->transit(self::TRANSITION_TO_DRAFT, 'post'); + $this->state = $this->workflow()->transit(self::TRANSITION_TO_DRAFT, $this); } public function publish(): void { - $this->state = $this->workflow()->transit(self::TRANSITION_PUBLISH, 'post'); + $this->state = $this->workflow()->transit(self::TRANSITION_PUBLISH, $this); } public function archive(): void { - $this->state = $this->workflow()->transit(self::TRANSITION_ARCHIVE, 'post'); + $this->state = $this->workflow()->transit(self::TRANSITION_ARCHIVE, $this); } - /** - * @return Post - */ - public static function drafted() + public static function drafted(): self { return new self(self::STATE_DRAFT); } diff --git a/examples/ContextUsingCustomMetadataTest.php b/examples/ContextUsingCustomMetadataTest.php index cd15ff1..bb2e254 100644 --- a/examples/ContextUsingCustomMetadataTest.php +++ b/examples/ContextUsingCustomMetadataTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\RegistryBuilder; +use Star\Component\State\StateContext; use Star\Component\State\StateMetadata; use Star\Component\State\StateTransition; @@ -215,18 +216,20 @@ public function getDestinationState(): string } } -final class ContextStub +final class ContextStub implements StateContext { - /** - * @var MyStateWorkflow|StateMetadata - */ - public $state; + public MyStateWorkflow $state; public function __construct() { $this->state = new MyStateWorkflow(); } + public function toStateContextIdentifier(): string + { + return 'ContextStub'; + } + public function publish(): void { $this->state = $this->state->transit('publish', $this); diff --git a/src/Builder/StateBuilder.php b/src/Builder/StateBuilder.php index 3033e4a..d246c93 100644 --- a/src/Builder/StateBuilder.php +++ b/src/Builder/StateBuilder.php @@ -73,8 +73,11 @@ public function create(string $currentState): StateMachine return new StateMachine($currentState, $this->registry, $this->listeners); } - public static function build(): StateBuilder - { - return new static(); + public static function build( + ?TransitionRegistry $registry = null, + ?EventRegistry $listeners = null, + ): StateBuilder { + // todo deprecate explict class in favor of interface + return new self($registry, $listeners); } } diff --git a/src/Callbacks/TransitionCallback.php b/src/Callbacks/TransitionCallback.php index de7107b..0e36485 100644 --- a/src/Callbacks/TransitionCallback.php +++ b/src/Callbacks/TransitionCallback.php @@ -3,28 +3,39 @@ namespace Star\Component\State\Callbacks; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; interface TransitionCallback { /** - * @param mixed $context + * @param mixed|StateContext $context * @param StateMachine $machine */ - public function beforeStateChange($context, StateMachine $machine): void; + public function beforeStateChange( + $context, + StateMachine $machine, + ): void; /** - * @param mixed $context + * @param mixed|StateContext $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void; + public function afterStateChange( + $context, + StateMachine $machine, + ): void; /** * @param InvalidStateTransitionException $exception - * @param mixed $context + * @param mixed|StateContext $context * @param StateMachine $machine * * @return string The new state to move to on failure */ - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string; + public function onFailure( + InvalidStateTransitionException $exception, + $context, + StateMachine $machine, + ): string; } diff --git a/src/Event/Adapter/ObjectAdapterContext.php b/src/Event/Adapter/ObjectAdapterContext.php new file mode 100644 index 0000000..4a0e8cf --- /dev/null +++ b/src/Event/Adapter/ObjectAdapterContext.php @@ -0,0 +1,38 @@ +object), + StateContext::class, + StateContext::class, + ), + E_USER_DEPRECATED, + ); + } + + public function toStateContextIdentifier(): string + { + $class = get_class($this->object); + return substr($class, strrpos($class, '\\') + 1); + } +} diff --git a/src/Event/Adapter/StringAdapterContext.php b/src/Event/Adapter/StringAdapterContext.php new file mode 100644 index 0000000..690f52e --- /dev/null +++ b/src/Event/Adapter/StringAdapterContext.php @@ -0,0 +1,33 @@ +context, + StateContext::class, + ), + E_USER_DEPRECATED, + ); + } + + public function toStateContextIdentifier(): string + { + return $this->context; + } +} diff --git a/src/Event/TransitionWasFailed.php b/src/Event/TransitionWasFailed.php index c74f13a..f650cff 100644 --- a/src/Event/TransitionWasFailed.php +++ b/src/Event/TransitionWasFailed.php @@ -2,17 +2,19 @@ namespace Star\Component\State\Event; +use Star\Component\State\StateContext; use Symfony\Contracts\EventDispatcher\Event; +use Throwable; final class TransitionWasFailed extends Event implements StateEvent { - private string $transition; - private \Throwable $exception; - - public function __construct(string $transition, \Throwable $exception) - { - $this->transition = $transition; - $this->exception = $exception; + public function __construct( + private readonly string $transition, + private readonly string $previousState, + private readonly string $destinationState, + private readonly StateContext $context, + private readonly Throwable $exception, + ) { } public function transition(): string @@ -20,6 +22,21 @@ public function transition(): string return $this->transition; } + public function getPreviousState(): string + { + return $this->previousState; + } + + public function getDestinationState(): string + { + return $this->destinationState; + } + + public function getContext(): StateContext + { + return $this->context; + } + public function exception(): \Throwable { return $this->exception; diff --git a/src/Event/TransitionWasRequested.php b/src/Event/TransitionWasRequested.php index 06c62f0..053f5f7 100644 --- a/src/Event/TransitionWasRequested.php +++ b/src/Event/TransitionWasRequested.php @@ -7,19 +7,36 @@ namespace Star\Component\State\Event; +use Star\Component\State\StateContext; use Symfony\Contracts\EventDispatcher\Event; final class TransitionWasRequested extends Event implements StateEvent { - private string $transition; - - public function __construct(string $transition) - { - $this->transition = $transition; + public function __construct( + private readonly string $transition, + private readonly string $previousState, + private readonly string $destinationState, + private readonly StateContext $context, + ) { } public function transition(): string { return $this->transition; } + + public function getPreviousState(): string + { + return $this->previousState; + } + + public function getDestinationState(): string + { + return $this->destinationState; + } + + public function getContext(): StateContext + { + return $this->context; + } } diff --git a/src/Event/TransitionWasSuccessful.php b/src/Event/TransitionWasSuccessful.php index 7ef6ef9..086ed10 100644 --- a/src/Event/TransitionWasSuccessful.php +++ b/src/Event/TransitionWasSuccessful.php @@ -7,19 +7,36 @@ namespace Star\Component\State\Event; +use Star\Component\State\StateContext; use Symfony\Contracts\EventDispatcher\Event; final class TransitionWasSuccessful extends Event implements StateEvent { - private string $transition; - - public function __construct(string $transition) - { - $this->transition = $transition; + public function __construct( + private readonly string $transition, + private readonly string $previousState, + private readonly string $destinationState, + private readonly StateContext $context, + ) { } public function transition(): string { return $this->transition; } + + public function getPreviousState(): string + { + return $this->previousState; + } + + public function getDestinationState(): string + { + return $this->destinationState; + } + + public function getContext(): StateContext + { + return $this->context; + } } diff --git a/src/InvalidStateTransitionException.php b/src/InvalidStateTransitionException.php index e7f6fd0..574396a 100644 --- a/src/InvalidStateTransitionException.php +++ b/src/InvalidStateTransitionException.php @@ -7,11 +7,17 @@ namespace Star\Component\State; +use Star\Component\State\Event\Adapter\ObjectAdapterContext; +use Star\Component\State\Event\Adapter\StringAdapterContext; +use function is_object; +use function is_scalar; +use function sprintf; + final class InvalidStateTransitionException extends \Exception { /** * @param string $transition - * @param string|object $context + * @param string|object|StateContext $context * @param string $currentState * * @return static @@ -21,12 +27,18 @@ public static function notAllowedTransition( $context, string $currentState ): self { - if (\is_object($context)) { - $context = \get_class($context); + if (is_scalar($context)) { + $context = new StringAdapterContext((string) $context); + } + + if (is_object($context) && !$context instanceof StateContext) { + $context = new ObjectAdapterContext($context); } + // todo statte context should have a toString() + return new static( - \sprintf( + sprintf( "The transition '%s' is not allowed when context '%s' is in state '%s'.", $transition, $context, diff --git a/src/StateContext.php b/src/StateContext.php new file mode 100644 index 0000000..c51b19a --- /dev/null +++ b/src/StateContext.php @@ -0,0 +1,17 @@ +currentState; + $transition = $this->states->getTransition($transitionName); + $newState = $transition->getDestinationState(); $this->listeners->dispatch( StateEventStore::BEFORE_TRANSITION, - new TransitionWasRequested($transitionName) + new TransitionWasRequested( + $transitionName, + $previous, + $newState, + $context, + ) ); - $transition = $this->states->getTransition($transitionName); $callback->beforeStateChange($context, $this); - $newState = $transition->getDestinationState(); $allowed = $this->states->transitionStartsFrom($transitionName, $this->currentState); if (!$allowed) { $exception = InvalidStateTransitionException::notAllowedTransition( @@ -68,7 +86,13 @@ public function transit( $this->listeners->dispatch( StateEventStore::FAILURE_TRANSITION, - new TransitionWasFailed($transitionName, $exception) + new TransitionWasFailed( + $transitionName, + $previous, + $newState, + $context, + $exception, + ) ); $newState = $callback->onFailure($exception, $context, $this); @@ -80,7 +104,12 @@ public function transit( $this->listeners->dispatch( StateEventStore::AFTER_TRANSITION, - new TransitionWasSuccessful($transitionName) + new TransitionWasSuccessful( + $transitionName, + $previous, + $newState, + $context, + ) ); return $this->currentState; diff --git a/tests/Context/TestStubContext.php b/tests/Context/TestStubContext.php new file mode 100644 index 0000000..b78ab61 --- /dev/null +++ b/tests/Context/TestStubContext.php @@ -0,0 +1,18 @@ +identifier; + } +} diff --git a/tests/StateMachineTest.php b/tests/StateMachineTest.php index 07d134e..a0512f2 100644 --- a/tests/StateMachineTest.php +++ b/tests/StateMachineTest.php @@ -7,11 +7,15 @@ namespace Star\Component\State; +use Context\TestContextWithInterface; use PHPUnit\Framework\TestCase; +use Star\Component\State\Builder\StateBuilder; +use Star\Component\State\Callbacks\BufferStateChanges; +use Star\Component\State\Context\TestStubContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Event\TransitionWasFailed; -use Star\Component\State\Event\TransitionWasSuccessful; use Star\Component\State\Event\TransitionWasRequested; +use Star\Component\State\Event\TransitionWasSuccessful; use Star\Component\State\Stub\EventRegistrySpy; use Star\Component\State\Transitions\OneToOneTransition; use stdClass; @@ -136,4 +140,65 @@ public function test_it_should_dispatch_an_event_before_a_transition_has_failed( self::assertCount(1, $events); self::assertContainsOnlyInstancesOf(TransitionWasFailed::class, $events); } + + public function test_it_should_invoke_before_state_change_callback(): void + { + $this->registry->addTransition(new OneToOneTransition('t', 'current', 'to')); + $buffer = new BufferStateChanges(); + + self::assertSame( + [], + $buffer->flushBuffer(), + ); + + $this->machine->transit( + 't', + 'context', + $buffer, + ); + + self::assertSame( + [ + 'context' => [ + 'beforeStateChange', + 'afterStateChange', + ], + ], + $buffer->flushBuffer(), + ); + } + + public function test_it_should_allow_to_transit_using_state_context(): void + { + $machine = StateBuilder::build(null, $this->events) + ->allowTransition('activate', 'left', 'right') + ->create('left'); + $context = new TestStubContext('post'); + + $machine->transit( + 'activate', + $context, + $callback = new BufferStateChanges(), + ); + + self::assertSame( + [ + + ], + $this->events->getDispatchedEvents('ddsa') + ); + self::assertSame( + [ + + ], + $callback->flushBuffer(), + ); + + self::fail('todo'); + } + + public function test_it_should_allow_handle_failure_with_state_context(): void + { + self::fail('todo'); + } } diff --git a/tests/TestContext.php b/tests/TestContext.php index 2543164..79fac4c 100644 --- a/tests/TestContext.php +++ b/tests/TestContext.php @@ -7,6 +7,10 @@ namespace Star\Component\State; +/** + * @deprecated Will be removed in 4.0. You should use StateContext. + * @see StateContext + */ final class TestContext { } From e7a1d910a86850f9718ecfb703ea1306cc60df5c Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 12:46:56 -0500 Subject: [PATCH 2/6] * Migrate all code to adapters for deprecation --- examples/CallbackStateTest.php | 12 ++- examples/DoctrineMappedContextTest.php | 8 +- src/Builder/StateBuilder.php | 19 +++-- src/Callbacks/AlwaysReturnStateOnFailure.php | 28 ++++--- .../AlwaysThrowExceptionOnFailure.php | 19 +++-- src/Callbacks/BufferStateChanges.php | 68 +++++++++++++++ src/Callbacks/CallClosureOnFailure.php | 14 ++-- src/Callbacks/CallContextMethodOnFailure.php | 19 +++-- src/Callbacks/NullCallback.php | 19 +++-- src/Callbacks/TransitionCallback.php | 12 ++- src/Event/Adapter/ObjectAdapterContext.php | 43 ++++++---- src/Event/Adapter/StringAdapterContext.php | 33 +++++--- src/InvalidStateTransitionException.php | 6 +- src/StateMachine.php | 5 +- tests/Callbacks/BufferStateChangesTest.php | 84 +++++++++++++++++++ tests/Context/TestStubContext.php | 10 ++- tests/StateBuilderTest.php | 4 +- tests/StateMachineTest.php | 63 ++++++++++---- tests/StateMetadataTest.php | 5 +- tests/TestContext.php | 6 +- 20 files changed, 376 insertions(+), 101 deletions(-) create mode 100644 src/Callbacks/BufferStateChanges.php create mode 100644 tests/Callbacks/BufferStateChangesTest.php diff --git a/examples/CallbackStateTest.php b/examples/CallbackStateTest.php index a5aad3d..e6b3a60 100644 --- a/examples/CallbackStateTest.php +++ b/examples/CallbackStateTest.php @@ -9,6 +9,7 @@ use Star\Component\State\Callbacks\TransitionCallback; use Star\Component\State\InvalidStateTransitionException; use Star\Component\State\RegistryBuilder; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; use Star\Component\State\StateMetadata; use Star\Component\State\StateTransition; @@ -58,7 +59,7 @@ public function test_workflow(): void } } -final class TurnStill +final class TurnStill implements StateContext { /** * @var TurnStillState|StateMetadata @@ -75,6 +76,11 @@ public function __construct() $this->state = new TurnStillState('locked'); } + public function toStateContextIdentifier(): string + { + return 'turn-still'; + } + public function pay(int $coin): void { $this->state = $this->state->transit( @@ -94,7 +100,7 @@ public function pass(): void { $this->state = $this->state->transit( 'pass', - 'turn-still', + $this, new CallClosureOnFailure( function () { return $this->state->transit('alarm', $this)->getCurrent(); @@ -105,7 +111,7 @@ function () { public function reset(): void { - $this->state = $this->state->transit('reset', 'turn-still'); + $this->state = $this->state->transit('reset', $this); } public function isLocked(): bool diff --git a/examples/DoctrineMappedContextTest.php b/examples/DoctrineMappedContextTest.php index 65c9e8a..1b808b4 100644 --- a/examples/DoctrineMappedContextTest.php +++ b/examples/DoctrineMappedContextTest.php @@ -15,6 +15,7 @@ use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; +use Star\Component\State\StateContext; use Star\Component\State\StateMetadata; final class DoctrineMappedContextTest extends TestCase @@ -85,7 +86,7 @@ private function save(MyEntity $entity): MyEntity /** * @Entity() */ -class MyEntity +class MyEntity implements StateContext { /** * @var int @@ -106,6 +107,11 @@ public function __construct() $this->state = new MyState(); } + public function toStateContextIdentifier(): string + { + throw new \RuntimeException(__METHOD__ . ' is not implemented yet.'); + } + public function isLocked(): bool { return $this->state->isInState('locked'); diff --git a/src/Builder/StateBuilder.php b/src/Builder/StateBuilder.php index d246c93..fe31e09 100644 --- a/src/Builder/StateBuilder.php +++ b/src/Builder/StateBuilder.php @@ -18,10 +18,19 @@ final class StateBuilder private TransitionRegistry $registry; private EventRegistry $listeners; - public function __construct() - { - $this->registry = new TransitionRegistry(); - $this->listeners = new EventDispatcherAdapter(); + public function __construct( + ?TransitionRegistry $registry = null, + ?EventRegistry $listeners = null + ) { + if (!$registry) { + $registry = new TransitionRegistry(); + } + $this->registry = $registry; + + if (!$listeners) { + $listeners = new EventDispatcherAdapter(); + } + $this->listeners = $listeners; } /** @@ -75,7 +84,7 @@ public function create(string $currentState): StateMachine public static function build( ?TransitionRegistry $registry = null, - ?EventRegistry $listeners = null, + ?EventRegistry $listeners = null ): StateBuilder { // todo deprecate explict class in favor of interface return new self($registry, $listeners); diff --git a/src/Callbacks/AlwaysReturnStateOnFailure.php b/src/Callbacks/AlwaysReturnStateOnFailure.php index f2f530e..f3fba76 100644 --- a/src/Callbacks/AlwaysReturnStateOnFailure.php +++ b/src/Callbacks/AlwaysReturnStateOnFailure.php @@ -3,6 +3,7 @@ namespace Star\Component\State\Callbacks; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; final class AlwaysReturnStateOnFailure implements TransitionCallback @@ -14,31 +15,34 @@ public function __construct(string $to) $this->to = $to; } - /** - * @param mixed $context - * @param StateMachine $machine - */ - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** - * @param mixed $context + * @param string|object|StateContext $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** * @param InvalidStateTransitionException $exception - * @param mixed $context + * @param string|object|StateContext $context * @param StateMachine $machine * * @return string */ - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string - { + public function onFailure( + InvalidStateTransitionException $exception, + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): string { return $this->to; } } diff --git a/src/Callbacks/AlwaysThrowExceptionOnFailure.php b/src/Callbacks/AlwaysThrowExceptionOnFailure.php index 6ab9e4e..1df6acb 100644 --- a/src/Callbacks/AlwaysThrowExceptionOnFailure.php +++ b/src/Callbacks/AlwaysThrowExceptionOnFailure.php @@ -11,16 +11,20 @@ final class AlwaysThrowExceptionOnFailure implements TransitionCallback * @param mixed $context * @param StateMachine $machine */ - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** * @param mixed $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** @@ -31,8 +35,11 @@ public function afterStateChange($context, StateMachine $machine): void * @return string * @throws InvalidStateTransitionException */ - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string - { + public function onFailure( + InvalidStateTransitionException $exception, + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): string { throw $exception; } } diff --git a/src/Callbacks/BufferStateChanges.php b/src/Callbacks/BufferStateChanges.php new file mode 100644 index 0000000..dc0b9f7 --- /dev/null +++ b/src/Callbacks/BufferStateChanges.php @@ -0,0 +1,68 @@ +> + */ + private array $buffer = []; + + /** + * @param mixed|StateContext $context + * @return string + */ + private function extractContextIdentifier($context): string + { + if (! $context instanceof StateContext) { + if (is_object($context)) { + $context = get_class($context); + } + } else { + $context = $context->toStateContextIdentifier(); + } + Assert::string($context, 'Context is expected to be a string. Got: %s'); + + return $context; + } + + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { + $this->buffer[$this->extractContextIdentifier($context)][] = __FUNCTION__; + } + + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { + $this->buffer[$this->extractContextIdentifier($context)][] = __FUNCTION__; + } + + public function onFailure( + InvalidStateTransitionException $exception, + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): string { + $this->buffer[$this->extractContextIdentifier($context)][] = get_class($exception); + + return ''; + } + + /** + * @return array> + */ + public function flushBuffer(): array + { + return $this->buffer; + } +} diff --git a/src/Callbacks/CallClosureOnFailure.php b/src/Callbacks/CallClosureOnFailure.php index dad8d4d..f418500 100644 --- a/src/Callbacks/CallClosureOnFailure.php +++ b/src/Callbacks/CallClosureOnFailure.php @@ -22,16 +22,20 @@ public function __construct(Closure $callback) * @param mixed $context * @param StateMachine $machine */ - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** * @param mixed $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** @@ -43,7 +47,7 @@ public function afterStateChange($context, StateMachine $machine): void */ public function onFailure( InvalidStateTransitionException $exception, - $context, + /* StateContext in 4.0 */ $context, StateMachine $machine ): string { $callback = $this->callback; diff --git a/src/Callbacks/CallContextMethodOnFailure.php b/src/Callbacks/CallContextMethodOnFailure.php index f244abc..f72b700 100644 --- a/src/Callbacks/CallContextMethodOnFailure.php +++ b/src/Callbacks/CallContextMethodOnFailure.php @@ -34,16 +34,20 @@ public function __construct( * @param mixed $context * @param StateMachine $machine */ - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** * @param mixed $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** @@ -53,8 +57,11 @@ public function afterStateChange($context, StateMachine $machine): void * * @return string */ - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string - { + public function onFailure( + InvalidStateTransitionException $exception, + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): string { $closure = function (array $args) use ($context) { $context->{$this->method}(...$args); }; diff --git a/src/Callbacks/NullCallback.php b/src/Callbacks/NullCallback.php index 6b0b4db..8f960d6 100644 --- a/src/Callbacks/NullCallback.php +++ b/src/Callbacks/NullCallback.php @@ -11,16 +11,20 @@ final class NullCallback implements TransitionCallback * @param mixed $context * @param StateMachine $machine */ - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** * @param mixed $context * @param StateMachine $machine */ - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): void { } /** @@ -30,8 +34,11 @@ public function afterStateChange($context, StateMachine $machine): void * * @return string */ - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string - { + public function onFailure( + InvalidStateTransitionException $exception, + /* StateContext in 4.0 */ $context, + StateMachine $machine + ): string { throw new \RuntimeException('Method ' . __METHOD__ . ' should never be called.'); } } diff --git a/src/Callbacks/TransitionCallback.php b/src/Callbacks/TransitionCallback.php index 0e36485..5d2d58d 100644 --- a/src/Callbacks/TransitionCallback.php +++ b/src/Callbacks/TransitionCallback.php @@ -11,18 +11,22 @@ interface TransitionCallback /** * @param mixed|StateContext $context * @param StateMachine $machine + * @deprecated $context will expect a type of StateContext in 4.0, you need to update your implementations. + * @see StateContext */ public function beforeStateChange( - $context, + /* StateContext in 4.0 */ $context, StateMachine $machine, ): void; /** * @param mixed|StateContext $context * @param StateMachine $machine + * @deprecated $context will expect a type of StateContext in 4.0, you need to update your implementations. + * @see StateContext */ public function afterStateChange( - $context, + /* StateContext in 4.0 */ $context, StateMachine $machine, ): void; @@ -32,10 +36,12 @@ public function afterStateChange( * @param StateMachine $machine * * @return string The new state to move to on failure + * @deprecated $context will expect a type of StateContext in 4.0, you need to update your implementations. + * @see StateContext */ public function onFailure( InvalidStateTransitionException $exception, - $context, + /* StateContext in 4.0 */ $context, StateMachine $machine, ): string; } diff --git a/src/Event/Adapter/ObjectAdapterContext.php b/src/Event/Adapter/ObjectAdapterContext.php index 4a0e8cf..8e38140 100644 --- a/src/Event/Adapter/ObjectAdapterContext.php +++ b/src/Event/Adapter/ObjectAdapterContext.php @@ -3,36 +3,51 @@ namespace Star\Component\State\Event\Adapter; use Star\Component\State\StateContext; -use function error_log; use function get_class; use function sprintf; use function strrpos; use function substr; +use function trigger_error; /** * Adapter for object that do not yet implement the interface. * @deprecated Will be removed in 4.0. Adapter during the transition to 4.0. */ -final readonly class ObjectAdapterContext implements StateContext +final class ObjectAdapterContext implements StateContext { + /** + * @var object + */ + private $object; + public function __construct( - private object $object, + object $object, + bool $logError = true // deprecated: will be removed in 4.0 ) { - @error_log( - sprintf( - 'Passing an object of type "%s" that do not implement "%s" is deprecated. ' . - 'The object should implementing "%s" interface.', - get_class($this->object), - StateContext::class, - StateContext::class, - ), - E_USER_DEPRECATED, - ); + $this->object = $object; + + if ($logError) { + @trigger_error( + sprintf( + 'Passing an object of type "%s" that do not implement "%s" is deprecated. ' . + 'The object should implementing "%s" interface.', + get_class($this->object), + StateContext::class, + StateContext::class, + ), + E_USER_DEPRECATED, + ); + } } public function toStateContextIdentifier(): string { $class = get_class($this->object); - return substr($class, strrpos($class, '\\') + 1); + $pos = strrpos($class, '\\'); + if ($pos > 0) { + $pos = $pos + 1; + } + + return substr($class, (int) $pos); } } diff --git a/src/Event/Adapter/StringAdapterContext.php b/src/Event/Adapter/StringAdapterContext.php index 690f52e..d96d35c 100644 --- a/src/Event/Adapter/StringAdapterContext.php +++ b/src/Event/Adapter/StringAdapterContext.php @@ -3,27 +3,36 @@ namespace Star\Component\State\Event\Adapter; use Star\Component\State\StateContext; -use function error_log; +use function trigger_error; use function sprintf; /** * Adapter for string context. * @deprecated Will be removed in 4.0. Adapter during the transition to 4.0. */ -final readonly class StringAdapterContext implements StateContext +final class StringAdapterContext implements StateContext { + /** + * @var string + */ + private $context; + public function __construct( - private string $context, + string $context, + bool $logError = true // deprecated: will be removed in 4.0 ) { - @error_log( - sprintf( - 'Passing a string context "%s" is deprecated. ' . - 'You should provide your own class implementing "%s" interface.', - $this->context, - StateContext::class, - ), - E_USER_DEPRECATED, - ); + $this->context = $context; + if ($logError) { + @trigger_error( + sprintf( + 'Passing a string context "%s" is deprecated. ' . + 'You should provide your own class implementing "%s" interface.', + $this->context, + StateContext::class, + ), + E_USER_DEPRECATED, + ); + } } public function toStateContextIdentifier(): string diff --git a/src/InvalidStateTransitionException.php b/src/InvalidStateTransitionException.php index 574396a..2519883 100644 --- a/src/InvalidStateTransitionException.php +++ b/src/InvalidStateTransitionException.php @@ -31,17 +31,17 @@ public static function notAllowedTransition( $context = new StringAdapterContext((string) $context); } - if (is_object($context) && !$context instanceof StateContext) { + if (!$context instanceof StateContext) { $context = new ObjectAdapterContext($context); } // todo statte context should have a toString() - return new static( + return new self( sprintf( "The transition '%s' is not allowed when context '%s' is in state '%s'.", $transition, - $context, + $context->toStateContextIdentifier(), $currentState ) ); diff --git a/src/StateMachine.php b/src/StateMachine.php index 57dec1b..7277bcd 100644 --- a/src/StateMachine.php +++ b/src/StateMachine.php @@ -46,7 +46,7 @@ public function __construct( */ public function transit( string $transitionName, - mixed $context, + $context, ?TransitionCallback $callback = null ): string { if (!$callback) { @@ -55,8 +55,7 @@ public function transit( if (is_scalar($context)) { $context = new StringAdapterContext((string) $context); } - - if (is_object($context) && !$context instanceof StateContext) { + if (!$context instanceof StateContext) { $context = new ObjectAdapterContext($context); } diff --git a/tests/Callbacks/BufferStateChangesTest.php b/tests/Callbacks/BufferStateChangesTest.php new file mode 100644 index 0000000..9b37f46 --- /dev/null +++ b/tests/Callbacks/BufferStateChangesTest.php @@ -0,0 +1,84 @@ +create(''); + + $buffer->beforeStateChange( + (object)[], + $machine + ); + $buffer->afterStateChange( + (object)[], + $machine + ); + + self::assertSame( + [ + 'stdClass' => [ + 0 => 'beforeStateChange', + 1 => 'afterStateChange', + ], + ], + $buffer->flushBuffer(), + ); + } + + public function test_it_should_buffer_context_as_string(): void + { + $buffer = new BufferStateChanges(); + $machine = StateBuilder::build() + ->create(''); + + $buffer->beforeStateChange( + 'stdClass', + $machine + ); + $buffer->afterStateChange( + 'stdClass', + $machine + ); + + self::assertSame( + [ + 'stdClass' => [ + 0 => 'beforeStateChange', + 1 => 'afterStateChange', + ], + ], + $buffer->flushBuffer(), + ); + } + + public function test_it_should_not_allow_non_string_context_in_before(): void + { + $buffer = new BufferStateChanges(); + $machine = StateBuilder::build() + ->create(''); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Context is expected to be a string. Got: integer'); + $buffer->beforeStateChange(42, $machine); + } + + public function test_it_should_not_allow_non_string_context_in_after(): void + { + $buffer = new BufferStateChanges(); + $machine = StateBuilder::build() + ->create(''); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Context is expected to be a string. Got: integer'); + $buffer->afterStateChange(42, $machine); + } +} diff --git a/tests/Context/TestStubContext.php b/tests/Context/TestStubContext.php index b78ab61..34746ca 100644 --- a/tests/Context/TestStubContext.php +++ b/tests/Context/TestStubContext.php @@ -4,11 +4,17 @@ use Star\Component\State\StateContext; -final readonly class TestStubContext implements StateContext +final class TestStubContext implements StateContext { + /** + * @var string + */ + private $identifier; + public function __construct( - private string $identifier, + string $identifier ) { + $this->identifier = $identifier; } public function toStateContextIdentifier(): string diff --git a/tests/StateBuilderTest.php b/tests/StateBuilderTest.php index 5a9c854..433d64d 100644 --- a/tests/StateBuilderTest.php +++ b/tests/StateBuilderTest.php @@ -15,7 +15,7 @@ public function test_it_should_allow_to_transition_to_next_state_when_multiple_s ->create('from'); self::assertTrue($machine->isInState('from')); - $machine->transit('t1', 'context'); + $machine->transit('t1', new TestContext()); self::assertTrue($machine->isInState('to')); } @@ -29,7 +29,7 @@ public function test_it_should_return_whether_the_current_state_has_attribute_af self::assertTrue($machine->isInState('from')); self::assertTrue($machine->hasAttribute('attr')); - $machine->transit('t1', 'context'); + $machine->transit('t1', new TestContext()); self::assertTrue($machine->isInState('to')); self::assertFalse($machine->hasAttribute('attr')); diff --git a/tests/StateMachineTest.php b/tests/StateMachineTest.php index a0512f2..e013897 100644 --- a/tests/StateMachineTest.php +++ b/tests/StateMachineTest.php @@ -7,11 +7,12 @@ namespace Star\Component\State; -use Context\TestContextWithInterface; use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\Callbacks\BufferStateChanges; use Star\Component\State\Context\TestStubContext; +use Star\Component\State\Event\Adapter\ObjectAdapterContext; +use Star\Component\State\Event\Adapter\StringAdapterContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Event\TransitionWasFailed; use Star\Component\State\Event\TransitionWasRequested; @@ -82,7 +83,7 @@ public function test_it_should_throw_exception_with_class_context_when_transitio $this->expectExceptionMessage( "The transition 't' is not allowed when context 'stdClass' is in state 'current'." ); - $this->machine->transit('t', new stdClass); + $this->machine->transit('t', new ObjectAdapterContext(new stdClass, false)); } public function test_it_should_throw_exception_with_context_as_string_when_transition_not_allowed(): void @@ -94,7 +95,7 @@ public function test_it_should_throw_exception_with_context_as_string_when_trans $this->expectExceptionMessage( "The transition 'transition' is not allowed when context 'c' is in state 'current'." ); - $this->machine->transit('transition', 'c'); + $this->machine->transit('transition', new StringAdapterContext('c', false)); } public function test_state_can_have_attribute(): void @@ -128,7 +129,7 @@ public function test_it_should_dispatch_an_event_before_a_transition_has_failed( { $this->registry->addTransition(new OneToOneTransition('t', 'from', 'to')); try { - $this->machine->transit('t', 'context'); + $this->machine->transit('t', new TestContext()); $this->fail('An exception should have been thrown'); } catch (Throwable $exception) { // silence it @@ -153,7 +154,7 @@ public function test_it_should_invoke_before_state_change_callback(): void $this->machine->transit( 't', - 'context', + new TestStubContext('context'), $buffer, ); @@ -183,22 +184,54 @@ public function test_it_should_allow_to_transit_using_state_context(): void self::assertSame( [ - + 'post' => [ + 'beforeStateChange', + 'afterStateChange', + ], ], - $this->events->getDispatchedEvents('ddsa') + $callback->flushBuffer() ); - self::assertSame( - [ - - ], - $callback->flushBuffer(), + self::assertCount( + 1, + $this->events->getDispatchedEvents(StateEventStore::BEFORE_TRANSITION) + ); + self::assertCount( + 1, + $this->events->getDispatchedEvents(StateEventStore::AFTER_TRANSITION) ); - - self::fail('todo'); } public function test_it_should_allow_handle_failure_with_state_context(): void { - self::fail('todo'); + $machine = StateBuilder::build(null, $this->events) + ->allowTransition('activate', 'right', 'left') + ->create('left'); + $context = new TestStubContext('post'); + $callback = new BufferStateChanges(); + + $machine->transit( + 'activate', + $context, + $callback + ); + + self::assertSame( + [ + 'post' => [ + 'beforeStateChange', + InvalidStateTransitionException::class, + 'afterStateChange', + ], + ], + $callback->flushBuffer() + ); + self::assertCount( + 1, + $this->events->getDispatchedEvents(StateEventStore::BEFORE_TRANSITION) + ); + self::assertCount( + 1, + $this->events->getDispatchedEvents(StateEventStore::AFTER_TRANSITION) + ); } } diff --git a/tests/StateMetadataTest.php b/tests/StateMetadataTest.php index af4df95..80a9515 100644 --- a/tests/StateMetadataTest.php +++ b/tests/StateMetadataTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\Callbacks\NullCallback; +use Star\Component\State\Context\TestStubContext; final class StateMetadataTest extends TestCase { @@ -24,7 +25,7 @@ public function test_it_should_check_if_has_attribute(): void public function test_it_should_transit(): void { $metadata = new CustomMetadata('from'); - $new = $metadata->transit('t1', 'context'); + $new = $metadata->transit('t1', new TestStubContext('context')); self::assertTrue($new->isInState('to')); } @@ -38,7 +39,7 @@ public function test_it_should_use_the_failure_callback_on_transit(): void ); $metadata->transit( 't1', - 'context', + new TestStubContext('context'), new NullCallback() ); } diff --git a/tests/TestContext.php b/tests/TestContext.php index 79fac4c..49b0408 100644 --- a/tests/TestContext.php +++ b/tests/TestContext.php @@ -11,6 +11,10 @@ * @deprecated Will be removed in 4.0. You should use StateContext. * @see StateContext */ -final class TestContext +final class TestContext implements StateContext { + public function toStateContextIdentifier(): string + { + return 'context'; + } } From 7cc7cc87a110f0fd4fc58a84cfa6d2e0ab9a4d44 Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 12:59:21 -0500 Subject: [PATCH 3/6] * Add adapter for userland --- RELEASE_NOTES.md | 16 ++++++++++++++++ .../Adapter => Context}/ObjectAdapterContext.php | 7 +++---- .../Adapter => Context}/StringAdapterContext.php | 9 ++++----- src/InvalidStateTransitionException.php | 9 ++++----- src/StateMachine.php | 9 ++++----- tests/StateMachineTest.php | 6 +++--- 6 files changed, 34 insertions(+), 22 deletions(-) rename src/{Event/Adapter => Context}/ObjectAdapterContext.php (84%) rename src/{Event/Adapter => Context}/StringAdapterContext.php (79%) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 51ee2db..65426d0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,21 @@ # Release notes +# 3.3.0 + +This release is a deprecation release. Deprecation warning will be issued when using a changed feature. + +* Introduction of a `StateContext` to replace the context that could be mixed. + +Before 3.3.0, a context could be `"post"` or an object that was converted to a FQCN. + +Starting with 4.0, we'll only accept implementation of `StateContext`. You will need your context to + implement the interface, or use the adapters [StringAdapterContext](src/Context/StringAdapterContext.php) or + [ObjectAdapterContext](src/Context/ObjectAdapterContext.php) if you don't want to add new custom code. + +# 3.2.0 + +[3.2.0](https://github.com/yvoyer/php-state/releases/tag/3.2.0) + # 3.1.0 * [#30](https://github.com/yvoyer/php-state/pull/30) diff --git a/src/Event/Adapter/ObjectAdapterContext.php b/src/Context/ObjectAdapterContext.php similarity index 84% rename from src/Event/Adapter/ObjectAdapterContext.php rename to src/Context/ObjectAdapterContext.php index 8e38140..d5819f5 100644 --- a/src/Event/Adapter/ObjectAdapterContext.php +++ b/src/Context/ObjectAdapterContext.php @@ -1,6 +1,6 @@ object = $object; - if ($logError) { + if ($triggerError) { @trigger_error( sprintf( 'Passing an object of type "%s" that do not implement "%s" is deprecated. ' . diff --git a/src/Event/Adapter/StringAdapterContext.php b/src/Context/StringAdapterContext.php similarity index 79% rename from src/Event/Adapter/StringAdapterContext.php rename to src/Context/StringAdapterContext.php index d96d35c..592f03c 100644 --- a/src/Event/Adapter/StringAdapterContext.php +++ b/src/Context/StringAdapterContext.php @@ -1,14 +1,13 @@ context = $context; - if ($logError) { + if ($triggerError) { @trigger_error( sprintf( 'Passing a string context "%s" is deprecated. ' . diff --git a/src/InvalidStateTransitionException.php b/src/InvalidStateTransitionException.php index 2519883..0225a45 100644 --- a/src/InvalidStateTransitionException.php +++ b/src/InvalidStateTransitionException.php @@ -7,9 +7,8 @@ namespace Star\Component\State; -use Star\Component\State\Event\Adapter\ObjectAdapterContext; -use Star\Component\State\Event\Adapter\StringAdapterContext; -use function is_object; +use Star\Component\State\Context\ObjectAdapterContext; +use Star\Component\State\Context\StringAdapterContext; use function is_scalar; use function sprintf; @@ -28,11 +27,11 @@ public static function notAllowedTransition( string $currentState ): self { if (is_scalar($context)) { - $context = new StringAdapterContext((string) $context); + $context = new StringAdapterContext((string) $context, true); } if (!$context instanceof StateContext) { - $context = new ObjectAdapterContext($context); + $context = new ObjectAdapterContext($context, true); } // todo statte context should have a toString() diff --git a/src/StateMachine.php b/src/StateMachine.php index 7277bcd..e69c3ce 100644 --- a/src/StateMachine.php +++ b/src/StateMachine.php @@ -10,13 +10,12 @@ use Closure; use Star\Component\State\Callbacks\AlwaysThrowExceptionOnFailure; use Star\Component\State\Callbacks\TransitionCallback; -use Star\Component\State\Event\Adapter\ObjectAdapterContext; -use Star\Component\State\Event\Adapter\StringAdapterContext; +use Star\Component\State\Context\ObjectAdapterContext; +use Star\Component\State\Context\StringAdapterContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Event\TransitionWasFailed; use Star\Component\State\Event\TransitionWasRequested; use Star\Component\State\Event\TransitionWasSuccessful; -use function is_object; use function is_scalar; final class StateMachine @@ -53,10 +52,10 @@ public function transit( $callback = new AlwaysThrowExceptionOnFailure(); } if (is_scalar($context)) { - $context = new StringAdapterContext((string) $context); + $context = new StringAdapterContext((string) $context, true); } if (!$context instanceof StateContext) { - $context = new ObjectAdapterContext($context); + $context = new ObjectAdapterContext($context, true); } $previous = $this->currentState; diff --git a/tests/StateMachineTest.php b/tests/StateMachineTest.php index e013897..8be9577 100644 --- a/tests/StateMachineTest.php +++ b/tests/StateMachineTest.php @@ -10,9 +10,9 @@ use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\Callbacks\BufferStateChanges; +use Star\Component\State\Context\ObjectAdapterContext; +use Star\Component\State\Context\StringAdapterContext; use Star\Component\State\Context\TestStubContext; -use Star\Component\State\Event\Adapter\ObjectAdapterContext; -use Star\Component\State\Event\Adapter\StringAdapterContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Event\TransitionWasFailed; use Star\Component\State\Event\TransitionWasRequested; @@ -95,7 +95,7 @@ public function test_it_should_throw_exception_with_context_as_string_when_trans $this->expectExceptionMessage( "The transition 'transition' is not allowed when context 'c' is in state 'current'." ); - $this->machine->transit('transition', new StringAdapterContext('c', false)); + $this->machine->transit('transition', new StringAdapterContext('c')); } public function test_state_can_have_attribute(): void From 4ddcfcebc19745b94ac689f880571640c56cc367 Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 13:07:18 -0500 Subject: [PATCH 4/6] * Add release branches builds --- .github/workflows/php.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index f363898..d1399e6 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -2,9 +2,9 @@ name: Php state machine on: push: - branches: [ master ] + branches: [ master, release/* ] pull_request: - branches: [ master ] + branches: [ master, release/* ] jobs: phpunit: From ae60b522d0c20388a65ca560fe8cf21f0715b55d Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 13:16:54 -0500 Subject: [PATCH 5/6] * Make event support 7.1 --- src/Event/TransitionWasFailed.php | 21 ++++++++++++++++----- src/Event/TransitionWasRequested.php | 17 +++++++++++++---- src/Event/TransitionWasSuccessful.php | 17 +++++++++++++---- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/Event/TransitionWasFailed.php b/src/Event/TransitionWasFailed.php index f650cff..f6bd4e4 100644 --- a/src/Event/TransitionWasFailed.php +++ b/src/Event/TransitionWasFailed.php @@ -8,13 +8,24 @@ final class TransitionWasFailed extends Event implements StateEvent { + private string $transition; + private string $previousState; + private string $destinationState; + private StateContext $context; + private Throwable $exception; + public function __construct( - private readonly string $transition, - private readonly string $previousState, - private readonly string $destinationState, - private readonly StateContext $context, - private readonly Throwable $exception, + string $transition, + string $previousState, + string $destinationState, + StateContext $context, + Throwable $exception ) { + $this->transition = $transition; + $this->previousState = $previousState; + $this->destinationState = $destinationState; + $this->context = $context; + $this->exception = $exception; } public function transition(): string diff --git a/src/Event/TransitionWasRequested.php b/src/Event/TransitionWasRequested.php index 053f5f7..80643ff 100644 --- a/src/Event/TransitionWasRequested.php +++ b/src/Event/TransitionWasRequested.php @@ -12,12 +12,21 @@ final class TransitionWasRequested extends Event implements StateEvent { + private string $transition; + private string $previousState; + private string $destinationState; + private StateContext $context; + public function __construct( - private readonly string $transition, - private readonly string $previousState, - private readonly string $destinationState, - private readonly StateContext $context, + string $transition, + string $previousState, + string $destinationState, + StateContext $context ) { + $this->transition = $transition; + $this->previousState = $previousState; + $this->destinationState = $destinationState; + $this->context = $context; } public function transition(): string diff --git a/src/Event/TransitionWasSuccessful.php b/src/Event/TransitionWasSuccessful.php index 086ed10..d652c3f 100644 --- a/src/Event/TransitionWasSuccessful.php +++ b/src/Event/TransitionWasSuccessful.php @@ -12,12 +12,21 @@ final class TransitionWasSuccessful extends Event implements StateEvent { + private string $transition; + private string $previousState; + private string $destinationState; + private StateContext $context; + public function __construct( - private readonly string $transition, - private readonly string $previousState, - private readonly string $destinationState, - private readonly StateContext $context, + string $transition, + string $previousState, + string $destinationState, + StateContext $context ) { + $this->transition = $transition; + $this->previousState = $previousState; + $this->destinationState = $destinationState; + $this->context = $context; } public function transition(): string From a1012aa42a45ee9ac8fcdefdb27c97102fe82c03 Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 13:24:44 -0500 Subject: [PATCH 6/6] * Cleanup --- src/InvalidStateTransitionException.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/InvalidStateTransitionException.php b/src/InvalidStateTransitionException.php index 0225a45..b707894 100644 --- a/src/InvalidStateTransitionException.php +++ b/src/InvalidStateTransitionException.php @@ -34,8 +34,6 @@ public static function notAllowedTransition( $context = new ObjectAdapterContext($context, true); } - // todo statte context should have a toString() - return new self( sprintf( "The transition '%s' is not allowed when context '%s' is in state '%s'.",