From 29b4f7df5496e941627da8eab32eaf45d8d1b264 Mon Sep 17 00:00:00 2001 From: Yannick Voyer Date: Sun, 14 Dec 2025 15:07:33 -0500 Subject: [PATCH] Fix #38 - Remove StateContext related deprecations * Use type hint and property promotion everywhere --- examples/CallbackStateTest.php | 39 +++++++------- examples/ContextUsingBuilderTest.php | 53 ++++++------------- examples/ContextUsingCustomMetadataTest.php | 2 +- examples/DoctrineMappedContextTest.php | 10 ++-- src/Builder/StateBuilder.php | 7 --- src/Callbacks/AlwaysReturnStateOnFailure.php | 33 ++++-------- .../AlwaysThrowExceptionOnFailure.php | 26 +++------ src/Callbacks/BufferStateChanges.php | 38 ++++--------- src/Callbacks/CallClosureOnFailure.php | 38 ++++--------- src/Callbacks/CallContextMethodOnFailure.php | 49 ++++------------- src/Callbacks/NullCallback.php | 31 ++++------- src/Callbacks/TransitionCallback.php | 17 ++---- src/Context/ObjectAdapterContext.php | 26 +-------- src/Context/StringAdapterContext.php | 24 +-------- src/DuplicateEntryException.php | 4 +- src/InvalidStateTransitionException.php | 23 ++------ src/NotFoundException.php | 4 +- src/StateMachine.php | 21 +------- src/StateMetadata.php | 19 ++----- src/Transitions/ManyToOneTransition.php | 17 +++--- src/Transitions/OneToOneTransition.php | 16 +++--- src/Transitions/ReadOnlyTransition.php | 10 ++-- src/Visitor/AttributeDumper.php | 4 -- src/Visitor/TransitionDumper.php | 8 --- tests/Callbacks/BufferStateChangesTest.php | 33 +++--------- tests/Callbacks/CallClosureOnFailureTest.php | 3 +- tests/Context/HardCodedTestContext.php | 13 +++++ tests/Context/ObjectAdapterContextTest.php | 25 +++++++++ tests/Context/TestStubContext.php | 10 +--- tests/StateBuilderTest.php | 8 +-- tests/StateMachineTest.php | 16 +++--- tests/StateMetadataTest.php | 8 +-- tests/TestContext.php | 15 ------ 33 files changed, 204 insertions(+), 446 deletions(-) create mode 100644 tests/Context/HardCodedTestContext.php create mode 100644 tests/Context/ObjectAdapterContextTest.php delete mode 100644 tests/TestContext.php diff --git a/examples/CallbackStateTest.php b/examples/CallbackStateTest.php index e6b3a60..cada833 100644 --- a/examples/CallbackStateTest.php +++ b/examples/CallbackStateTest.php @@ -7,6 +7,7 @@ use Star\Component\State\Callbacks\CallContextMethodOnFailure; use Star\Component\State\Callbacks\CallClosureOnFailure; use Star\Component\State\Callbacks\TransitionCallback; +use Star\Component\State\Context\StringAdapterContext; use Star\Component\State\InvalidStateTransitionException; use Star\Component\State\RegistryBuilder; use Star\Component\State\StateContext; @@ -61,15 +62,9 @@ public function test_workflow(): void final class TurnStill implements StateContext { - /** - * @var TurnStillState|StateMetadata - */ - private $state; + private TurnStillState|StateMetadata $state; - /** - * @var int - */ - private $coins = 0; + private int $coins = 0; public function __construct() { @@ -142,9 +137,6 @@ public function getName(): string return 'pay'; } - /** - * @param RegistryBuilder $registry - */ public function onRegister(RegistryBuilder $registry): void { $registry->registerStartingState($this->getName(), 'locked', []); @@ -159,26 +151,31 @@ public function getDestinationState(): string final class TriggerAlarm implements TransitionCallback { - public function beforeStateChange($context, StateMachine $machine): void - { + public function beforeStateChange( + StateContext $context, + StateMachine $machine, + ): void { } - public function afterStateChange($context, StateMachine $machine): void - { + public function afterStateChange( + StateContext $context, + StateMachine $machine, + ): void { } - public function onFailure(InvalidStateTransitionException $exception, $context, StateMachine $machine): string - { - return $machine->transit('alarm', 'turnstill'); + public function onFailure( + InvalidStateTransitionException $exception, + StateContext $context, + StateMachine $machine, + ): string { + return $machine->transit('alarm', new StringAdapterContext('turnstill')); } } final class TurnStillState extends StateMetadata { /** - * Returns the state workflow configuration. - * - * @param StateBuilder $builder + * Configure the state workflow configuration. */ protected function configure(StateBuilder $builder): void { diff --git a/examples/ContextUsingBuilderTest.php b/examples/ContextUsingBuilderTest.php index b63fb9d..b1d5672 100644 --- a/examples/ContextUsingBuilderTest.php +++ b/examples/ContextUsingBuilderTest.php @@ -2,6 +2,7 @@ namespace Star\Component\State\Example; +use PHPUnit\Framework\Attributes\Depends; use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\InvalidStateTransitionException; @@ -34,10 +35,8 @@ public function test_post_should_be_archived(): void $this->assertTrue($post->isArchived()); } - /** - * @depends test_post_should_be_draft - * @depends test_post_should_be_published - */ + #[Depends('test_post_should_be_draft')] + #[Depends('test_post_should_be_published')] public function test_it_should_not_allow_from_draft_to_draft(): void { $post = Post::drafted(); @@ -50,10 +49,8 @@ public function test_it_should_not_allow_from_draft_to_draft(): void $post->moveToDraft(); } - /** - * @depends test_post_should_be_draft - * @depends test_post_should_be_published - */ + #[Depends('test_post_should_be_draft')] + #[Depends('test_post_should_be_published')] public function test_it_should_allow_from_draft_to_published(): void { $post = Post::drafted(); @@ -65,10 +62,8 @@ public function test_it_should_allow_from_draft_to_published(): void $this->assertTrue($post->isPublished()); } - /** - * @depends test_post_should_be_draft - * @depends test_post_should_be_published - */ + #[Depends('test_post_should_be_draft')] + #[Depends('test_post_should_be_published')] public function test_it_should_not_allow_from_published_to_published(): void { $post = Post::published(); @@ -81,10 +76,8 @@ public function test_it_should_not_allow_from_published_to_published(): void $post->publish(); } - /** - * @depends test_post_should_be_draft - * @depends test_post_should_be_published - */ + #[Depends('test_post_should_be_draft')] + #[Depends('test_post_should_be_published')] public function test_it_should_allow_from_published_to_draft(): void { $post = Post::published(); @@ -95,9 +88,7 @@ public function test_it_should_allow_from_published_to_draft(): void $this->assertTrue($post->isDraft()); } - /** - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_archived')] public function test_it_should_not_allow_from_draft_to_archived(): void { $post = Post::drafted(); @@ -110,9 +101,7 @@ public function test_it_should_not_allow_from_draft_to_archived(): void $post->archive(); } - /** - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_archived')] public function test_it_should_allow_from_published_to_archived(): void { $post = Post::published(); @@ -123,9 +112,7 @@ public function test_it_should_allow_from_published_to_archived(): void $this->assertTrue($post->isArchived()); } - /** - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_archived')] public function test_it_should_not_allow_from_archived_to_archived(): void { $post = Post::archived(); @@ -138,9 +125,7 @@ public function test_it_should_not_allow_from_archived_to_archived(): void $post->archive(); } - /** - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_archived')] public function test_it_should_not_allow_from_archived_to_draft(): void { $post = Post::archived(); @@ -153,9 +138,7 @@ public function test_it_should_not_allow_from_archived_to_draft(): void $post->moveToDraft(); } - /** - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_archived')] public function test_it_should_not_allow_from_archived_to_published(): void { $post = Post::archived(); @@ -168,11 +151,9 @@ public function test_it_should_not_allow_from_archived_to_published(): void $post->publish(); } - /** - * @depends test_post_should_be_draft - * @depends test_post_should_be_published - * @depends test_post_should_be_archived - */ + #[Depends('test_post_should_be_draft')] + #[Depends('test_post_should_be_published')] + #[Depends('test_post_should_be_archived')] public function test_it_should_allow_to_define_attributes_on_state(): void { $this->assertFalse(Post::drafted()->isActive()); diff --git a/examples/ContextUsingCustomMetadataTest.php b/examples/ContextUsingCustomMetadataTest.php index bb2e254..c383e9f 100644 --- a/examples/ContextUsingCustomMetadataTest.php +++ b/examples/ContextUsingCustomMetadataTest.php @@ -218,7 +218,7 @@ public function getDestinationState(): string final class ContextStub implements StateContext { - public MyStateWorkflow $state; + public MyStateWorkflow|StateMetadata $state; public function __construct() { diff --git a/examples/DoctrineMappedContextTest.php b/examples/DoctrineMappedContextTest.php index 79ca559..dd4cdce 100644 --- a/examples/DoctrineMappedContextTest.php +++ b/examples/DoctrineMappedContextTest.php @@ -10,9 +10,11 @@ use Doctrine\ORM\Tools\SchemaTool; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; +use RuntimeException; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\StateContext; use Star\Component\State\StateMetadata; +use function extension_loaded; final class DoctrineMappedContextTest extends TestCase { @@ -20,7 +22,7 @@ final class DoctrineMappedContextTest extends TestCase public function setUp(): void { - if (!\extension_loaded('pdo_sqlite')) { + if (!extension_loaded('pdo_sqlite')) { $this->markTestSkipped('Sqlite extension is needed'); } @@ -83,13 +85,13 @@ private function save(MyEntity $entity): MyEntity #[ORM\Entity] class MyEntity implements StateContext { - #[ORM\Id()] + #[ORM\Id] #[ORM\GeneratedValue(strategy: "AUTO")] #[ORM\Column(name: "id", type: "integer")] public int $id; #[ORM\Embedded(class: "MyState", columnPrefix: "my_")] - private MyState $state; + private MyState|StateMetadata $state; public function __construct() { @@ -98,7 +100,7 @@ public function __construct() public function toStateContextIdentifier(): string { - throw new \RuntimeException(__METHOD__ . ' is not implemented yet.'); + throw new RuntimeException(__METHOD__ . ' is not implemented yet.'); } public function isLocked(): bool diff --git a/src/Builder/StateBuilder.php b/src/Builder/StateBuilder.php index 1caa39b..e518013 100644 --- a/src/Builder/StateBuilder.php +++ b/src/Builder/StateBuilder.php @@ -36,11 +36,7 @@ public function __construct( } /** - * @param string $name * @param string|string[] $from - * @param string $to - * - * @return StateBuilder */ public function allowTransition(string $name, string|array $from, string $to): StateBuilder { @@ -65,10 +61,7 @@ public function allowCustomTransition(StateTransition $transition): void } /** - * @param string $attribute The attribute * @param string|string[] $states The list of states that this attribute applies to - * - * @return StateBuilder */ public function addAttribute(string $attribute, string|array $states): StateBuilder { diff --git a/src/Callbacks/AlwaysReturnStateOnFailure.php b/src/Callbacks/AlwaysReturnStateOnFailure.php index f3fba76..6e08f53 100644 --- a/src/Callbacks/AlwaysReturnStateOnFailure.php +++ b/src/Callbacks/AlwaysReturnStateOnFailure.php @@ -6,42 +6,29 @@ use Star\Component\State\StateContext; use Star\Component\State\StateMachine; -final class AlwaysReturnStateOnFailure implements TransitionCallback +final readonly class AlwaysReturnStateOnFailure implements TransitionCallback { - private string $to; - - public function __construct(string $to) - { - $this->to = $to; + public function __construct( + private string $to, + ) { } public function beforeStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param string|object|StateContext $context - * @param StateMachine $machine - */ public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param InvalidStateTransitionException $exception - * @param string|object|StateContext $context - * @param StateMachine $machine - * - * @return string - */ public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): string { return $this->to; } diff --git a/src/Callbacks/AlwaysThrowExceptionOnFailure.php b/src/Callbacks/AlwaysThrowExceptionOnFailure.php index 1df6acb..cc97221 100644 --- a/src/Callbacks/AlwaysThrowExceptionOnFailure.php +++ b/src/Callbacks/AlwaysThrowExceptionOnFailure.php @@ -3,42 +3,30 @@ namespace Star\Component\State\Callbacks; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; final class AlwaysThrowExceptionOnFailure implements TransitionCallback { - /** - * @param mixed $context - * @param StateMachine $machine - */ public function beforeStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } /** - * @param InvalidStateTransitionException $exception - * @param mixed $context - * @param StateMachine $machine - * - * @return string * @throws InvalidStateTransitionException */ public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): string { throw $exception; } diff --git a/src/Callbacks/BufferStateChanges.php b/src/Callbacks/BufferStateChanges.php index dc0b9f7..0bdf7ea 100644 --- a/src/Callbacks/BufferStateChanges.php +++ b/src/Callbacks/BufferStateChanges.php @@ -5,9 +5,7 @@ use Star\Component\State\InvalidStateTransitionException; use Star\Component\State\StateContext; use Star\Component\State\StateMachine; -use Webmozart\Assert\Assert; use function get_class; -use function is_object; final class BufferStateChanges implements TransitionCallback { @@ -16,44 +14,26 @@ final class BufferStateChanges implements TransitionCallback */ 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 + StateContext $context, + StateMachine $machine, ): void { - $this->buffer[$this->extractContextIdentifier($context)][] = __FUNCTION__; + $this->buffer[$context->toStateContextIdentifier()][] = __FUNCTION__; } public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { - $this->buffer[$this->extractContextIdentifier($context)][] = __FUNCTION__; + $this->buffer[$context->toStateContextIdentifier()][] = __FUNCTION__; } public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): string { - $this->buffer[$this->extractContextIdentifier($context)][] = get_class($exception); + $this->buffer[$context->toStateContextIdentifier()][] = get_class($exception); return ''; } diff --git a/src/Callbacks/CallClosureOnFailure.php b/src/Callbacks/CallClosureOnFailure.php index f418500..7f93152 100644 --- a/src/Callbacks/CallClosureOnFailure.php +++ b/src/Callbacks/CallClosureOnFailure.php @@ -5,50 +5,34 @@ use Closure; use InvalidArgumentException; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; use function is_string; use function sprintf; -final class CallClosureOnFailure implements TransitionCallback +final readonly class CallClosureOnFailure implements TransitionCallback { - private Closure $callback; - - public function __construct(Closure $callback) - { - $this->callback = $callback; + public function __construct( + private Closure $callback, + ) { } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function beforeStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param InvalidStateTransitionException $exception - * @param mixed $context - * @param StateMachine $machine - * - * @return string - */ public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): string { $callback = $this->callback; $return = $callback($context); diff --git a/src/Callbacks/CallContextMethodOnFailure.php b/src/Callbacks/CallContextMethodOnFailure.php index f72b700..18a82ab 100644 --- a/src/Callbacks/CallContextMethodOnFailure.php +++ b/src/Callbacks/CallContextMethodOnFailure.php @@ -3,64 +3,37 @@ namespace Star\Component\State\Callbacks; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; -final class CallContextMethodOnFailure implements TransitionCallback +final readonly class CallContextMethodOnFailure implements TransitionCallback { - private string $to; - private string $method; - /** - * @var mixed[] - */ - private array $args; - - /** - * @param string $to - * @param string $method * @param mixed[] $args */ public function __construct( - string $to, - string $method, - array $args + private string $to, + private string $method, + private array $args ) { - $this->to = $to; - $this->method = $method; - $this->args = $args; } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function beforeStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param InvalidStateTransitionException $exception - * @param mixed|object $context - * @param StateMachine $machine - * - * @return string - */ public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $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 8f960d6..c7d6291 100644 --- a/src/Callbacks/NullCallback.php +++ b/src/Callbacks/NullCallback.php @@ -2,43 +2,30 @@ namespace Star\Component\State\Callbacks; +use RuntimeException; use Star\Component\State\InvalidStateTransitionException; +use Star\Component\State\StateContext; use Star\Component\State\StateMachine; final class NullCallback implements TransitionCallback { - /** - * @param mixed $context - * @param StateMachine $machine - */ public function beforeStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param mixed $context - * @param StateMachine $machine - */ public function afterStateChange( - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): void { } - /** - * @param InvalidStateTransitionException $exception - * @param mixed $context - * @param StateMachine $machine - * - * @return string - */ public function onFailure( InvalidStateTransitionException $exception, - /* StateContext in 4.0 */ $context, - StateMachine $machine + StateContext $context, + StateMachine $machine, ): string { - throw new \RuntimeException('Method ' . __METHOD__ . ' should never be called.'); + throw new RuntimeException('Method ' . __METHOD__ . ' should never be called.'); } } diff --git a/src/Callbacks/TransitionCallback.php b/src/Callbacks/TransitionCallback.php index 5d2d58d..844b59f 100644 --- a/src/Callbacks/TransitionCallback.php +++ b/src/Callbacks/TransitionCallback.php @@ -9,39 +9,28 @@ 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( - /* StateContext in 4.0 */ $context, + StateContext $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( - /* StateContext in 4.0 */ $context, + StateContext $context, StateMachine $machine, ): void; /** - * @param InvalidStateTransitionException $exception - * @param mixed|StateContext $context - * @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, - /* StateContext in 4.0 */ $context, + StateContext $context, StateMachine $machine, ): string; } diff --git a/src/Context/ObjectAdapterContext.php b/src/Context/ObjectAdapterContext.php index d5819f5..cefef8a 100644 --- a/src/Context/ObjectAdapterContext.php +++ b/src/Context/ObjectAdapterContext.php @@ -4,39 +4,17 @@ use Star\Component\State\StateContext; 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. */ -final class ObjectAdapterContext implements StateContext +final readonly class ObjectAdapterContext implements StateContext { - /** - * @var object - */ - private $object; - public function __construct( - object $object, - bool $triggerError = false // deprecated: will be removed in 4.0 + private object $object, ) { - $this->object = $object; - - if ($triggerError) { - @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 diff --git a/src/Context/StringAdapterContext.php b/src/Context/StringAdapterContext.php index 592f03c..2c09c72 100644 --- a/src/Context/StringAdapterContext.php +++ b/src/Context/StringAdapterContext.php @@ -3,35 +3,15 @@ namespace Star\Component\State\Context; use Star\Component\State\StateContext; -use function sprintf; -use function trigger_error; /** * Adapter for string context. */ -final class StringAdapterContext implements StateContext +final readonly class StringAdapterContext implements StateContext { - /** - * @var string - */ - private $context; - public function __construct( - string $context, - bool $triggerError = false // deprecated: will be removed in 4.0 + private string $context, ) { - $this->context = $context; - if ($triggerError) { - @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/DuplicateEntryException.php b/src/DuplicateEntryException.php index d6d7bcd..2d3a91d 100644 --- a/src/DuplicateEntryException.php +++ b/src/DuplicateEntryException.php @@ -2,12 +2,14 @@ namespace Star\Component\State; +use function sprintf; + final class DuplicateEntryException extends \LogicException { public static function duplicateTransition(string $transition): self { return new self( - \sprintf("The transition '%s' is already registered.", $transition) + sprintf("The transition '%s' is already registered.", $transition) ); } } diff --git a/src/InvalidStateTransitionException.php b/src/InvalidStateTransitionException.php index 0ba254a..4439af8 100644 --- a/src/InvalidStateTransitionException.php +++ b/src/InvalidStateTransitionException.php @@ -2,33 +2,16 @@ namespace Star\Component\State; -use Star\Component\State\Context\ObjectAdapterContext; -use Star\Component\State\Context\StringAdapterContext; -use function is_scalar; +use Exception; use function sprintf; -final class InvalidStateTransitionException extends \Exception +final class InvalidStateTransitionException extends Exception { - /** - * @param string $transition - * @param string|object|StateContext $context - * @param string $currentState - * - * @return static - */ public static function notAllowedTransition( string $transition, - $context, + StateContext $context, string $currentState ): self { - if (is_scalar($context)) { - $context = new StringAdapterContext((string) $context, true); - } - - if (!$context instanceof StateContext) { - $context = new ObjectAdapterContext($context, true); - } - return new self( sprintf( "The transition '%s' is not allowed when context '%s' is in state '%s'.", diff --git a/src/NotFoundException.php b/src/NotFoundException.php index e4c5c21..8bd9285 100644 --- a/src/NotFoundException.php +++ b/src/NotFoundException.php @@ -2,11 +2,13 @@ namespace Star\Component\State; +use function sprintf; + final class NotFoundException extends \Exception { public static function stateNotFound(string $name): self { - return new self(\sprintf("The state '%s' could not be found.", $name)); + return new self(sprintf("The state '%s' could not be found.", $name)); } public static function transitionNotFound(string $name): self diff --git a/src/StateMachine.php b/src/StateMachine.php index 1516735..4a968fe 100644 --- a/src/StateMachine.php +++ b/src/StateMachine.php @@ -5,13 +5,10 @@ use Closure; use Star\Component\State\Callbacks\AlwaysThrowExceptionOnFailure; use Star\Component\State\Callbacks\TransitionCallback; -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_scalar; final class StateMachine { @@ -30,29 +27,18 @@ public function __construct( } /** - * @param string $transitionName The transition name - * @param string|object|StateContext $context - * @param TransitionCallback|null $callback - * * @return string The next state to store on your context * @throws InvalidStateTransitionException * @throws NotFoundException */ public function transit( string $transitionName, - $context, + StateContext $context, ?TransitionCallback $callback = null ): string { - // todo deprecate mixed to use StateContext if (!$callback) { $callback = new AlwaysThrowExceptionOnFailure(); } - if (is_scalar($context)) { - $context = new StringAdapterContext((string) $context, true); - } - if (!$context instanceof StateContext) { - $context = new ObjectAdapterContext($context, true); - } $previous = $this->currentState; $transition = $this->states->getTransition($transitionName); @@ -109,11 +95,6 @@ public function transit( return $this->currentState; } - /** - * @param string $stateName - * @return bool - * @throws NotFoundException - */ public function isInState(string $stateName): bool { if (!$this->states->hasState($stateName)) { diff --git a/src/StateMetadata.php b/src/StateMetadata.php index b3310c5..44c6279 100644 --- a/src/StateMetadata.php +++ b/src/StateMetadata.php @@ -7,14 +7,12 @@ abstract class StateMetadata { - protected string $current; - /** - * @param string $initial The initial state name + * @param string $current The initial state name */ - public function __construct(string $initial) - { - $this->current = $initial; + public function __construct( + protected string $current, + ) { } /** @@ -32,16 +30,9 @@ private function getMachine(): StateMachine return $builder->create($this->current); } - /** - * @param string $name - * @param string|object $context - * @param TransitionCallback|null $callback - * - * @return static - */ final public function transit( string $name, - $context, + StateContext $context, ?TransitionCallback $callback = null ): StateMetadata { $this->current = $this->getMachine()->transit($name, $context, $callback); diff --git a/src/Transitions/ManyToOneTransition.php b/src/Transitions/ManyToOneTransition.php index bf8be95..4845bc8 100644 --- a/src/Transitions/ManyToOneTransition.php +++ b/src/Transitions/ManyToOneTransition.php @@ -5,23 +5,22 @@ use Star\Component\State\RegistryBuilder; use Star\Component\State\StateTransition; use Webmozart\Assert\Assert; +use function count; -final class ManyToOneTransition implements StateTransition +final readonly class ManyToOneTransition implements StateTransition { - private string $name; - /** * @var string[] */ private array $fromStates; - private string $to; - public function __construct(string $name, string $to, string ...$fromStates) - { - $this->name = $name; - Assert::greaterThanEq(\count($fromStates), 1, 'Expected at least %2$s state. Got: %s'); + public function __construct( + private string $name, + private string $to, + string ...$fromStates, + ) { $this->fromStates = $fromStates; - $this->to = $to; + Assert::greaterThanEq(count($this->fromStates), 1, 'Expected at least %2$s state. Got: %s'); } public function getName(): string diff --git a/src/Transitions/OneToOneTransition.php b/src/Transitions/OneToOneTransition.php index b224c14..7139d1d 100644 --- a/src/Transitions/OneToOneTransition.php +++ b/src/Transitions/OneToOneTransition.php @@ -5,17 +5,13 @@ use Star\Component\State\RegistryBuilder; use Star\Component\State\StateTransition; -final class OneToOneTransition implements StateTransition +final readonly class OneToOneTransition implements StateTransition { - private string $name; - private string $from; - private string $to; - - public function __construct(string $name, string $from, string $to) - { - $this->name = $name; - $this->from = $from; - $this->to = $to; + public function __construct( + private string $name, + private string $from, + private string $to, + ) { } public function getName(): string diff --git a/src/Transitions/ReadOnlyTransition.php b/src/Transitions/ReadOnlyTransition.php index d149d86..d9437b2 100644 --- a/src/Transitions/ReadOnlyTransition.php +++ b/src/Transitions/ReadOnlyTransition.php @@ -5,13 +5,11 @@ use Star\Component\State\RegistryBuilder; use Star\Component\State\StateTransition; -final class ReadOnlyTransition implements StateTransition +final readonly class ReadOnlyTransition implements StateTransition { - private string $destination; - - public function __construct(string $destination) - { - $this->destination = $destination; + public function __construct( + private string $destination, + ) { } public function getName(): string diff --git a/src/Visitor/AttributeDumper.php b/src/Visitor/AttributeDumper.php index 53f212b..3a7e420 100644 --- a/src/Visitor/AttributeDumper.php +++ b/src/Visitor/AttributeDumper.php @@ -20,10 +20,6 @@ public function getStructure(): array return $this->attributesByStates; } - /** - * @param string $name - * @param string[] $attributes - */ public function visitState(string $name, array $attributes): void { $this->attributesByStates[$name] = $attributes; diff --git a/src/Visitor/TransitionDumper.php b/src/Visitor/TransitionDumper.php index d0e1924..2ce6bd3 100644 --- a/src/Visitor/TransitionDumper.php +++ b/src/Visitor/TransitionDumper.php @@ -25,19 +25,11 @@ public function visitTransition(string $name): void $this->currentTransition = $name; } - /** - * @param string $state - * @param string[] $attributes - */ public function visitFromState(string $state, array $attributes): void { $this->structure[$this->currentTransition]['from'][] = $state; } - /** - * @param string $state - * @param string[] $attributes - */ public function visitToState(string $state, array $attributes): void { $this->structure[$this->currentTransition]['to'][] = $state; diff --git a/tests/Callbacks/BufferStateChangesTest.php b/tests/Callbacks/BufferStateChangesTest.php index 9b37f46..7e1e2d2 100644 --- a/tests/Callbacks/BufferStateChangesTest.php +++ b/tests/Callbacks/BufferStateChangesTest.php @@ -2,9 +2,10 @@ namespace Star\Component\State\Callbacks; -use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; +use Star\Component\State\Context\ObjectAdapterContext; +use Star\Component\State\Context\StringAdapterContext; final class BufferStateChangesTest extends TestCase { @@ -15,11 +16,11 @@ public function test_it_should_buffer_context_as_object(): void ->create(''); $buffer->beforeStateChange( - (object)[], + new ObjectAdapterContext((object)[]), $machine ); $buffer->afterStateChange( - (object)[], + new ObjectAdapterContext((object)[]), $machine ); @@ -41,11 +42,11 @@ public function test_it_should_buffer_context_as_string(): void ->create(''); $buffer->beforeStateChange( - 'stdClass', + new StringAdapterContext('stdClass'), $machine ); $buffer->afterStateChange( - 'stdClass', + new StringAdapterContext('stdClass'), $machine ); @@ -59,26 +60,4 @@ public function test_it_should_buffer_context_as_string(): void $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/Callbacks/CallClosureOnFailureTest.php b/tests/Callbacks/CallClosureOnFailureTest.php index 5a21c7a..81f6922 100644 --- a/tests/Callbacks/CallClosureOnFailureTest.php +++ b/tests/Callbacks/CallClosureOnFailureTest.php @@ -4,6 +4,7 @@ use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use Star\Component\State\Context\StringAdapterContext; use Star\Component\State\EventRegistry; use Star\Component\State\InvalidStateTransitionException; use Star\Component\State\StateMachine; @@ -22,7 +23,7 @@ function () { $this->expectExceptionMessage('Callback should be returning a string, type "integer" returned.'); $handler->onFailure( new InvalidStateTransitionException(), - 'context', + new StringAdapterContext('context'), new StateMachine( 'state', $this->createStub(StateRegistry::class), diff --git a/tests/Context/HardCodedTestContext.php b/tests/Context/HardCodedTestContext.php new file mode 100644 index 0000000..e7cc193 --- /dev/null +++ b/tests/Context/HardCodedTestContext.php @@ -0,0 +1,13 @@ +toStateContextIdentifier(), + ); + } + + public function test_it_should_return_context_of_class_from_namespace(): void + { + self::assertSame( + 'HardCodedTestContext', + (new ObjectAdapterContext(new HardCodedTestContext()))->toStateContextIdentifier(), + ); + } +} diff --git a/tests/Context/TestStubContext.php b/tests/Context/TestStubContext.php index 34746ca..b78ab61 100644 --- a/tests/Context/TestStubContext.php +++ b/tests/Context/TestStubContext.php @@ -4,17 +4,11 @@ use Star\Component\State\StateContext; -final class TestStubContext implements StateContext +final readonly class TestStubContext implements StateContext { - /** - * @var string - */ - private $identifier; - public function __construct( - string $identifier + private string $identifier, ) { - $this->identifier = $identifier; } public function toStateContextIdentifier(): string diff --git a/tests/StateBuilderTest.php b/tests/StateBuilderTest.php index 1e27d4d..08ef167 100644 --- a/tests/StateBuilderTest.php +++ b/tests/StateBuilderTest.php @@ -4,6 +4,8 @@ use PHPUnit\Framework\TestCase; use Star\Component\State\Builder\StateBuilder; +use Star\Component\State\Context\StringAdapterContext; +use Star\Component\State\Context\HardCodedTestContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Port\Symfony\EventDispatcherAdapter; @@ -17,7 +19,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', new TestContext()); + $machine->transit('t1', new HardCodedTestContext()); self::assertTrue($machine->isInState('to')); } @@ -31,7 +33,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', new TestContext()); + $machine->transit('t1', new HardCodedTestContext()); self::assertTrue($machine->isInState('to')); self::assertFalse($machine->hasAttribute('attr')); @@ -58,7 +60,7 @@ public function test_it_should_dispatch_event_on_transit(): void ->allowTransition('t', 'from', 'to') ->create('from'); - $machine->transit('t', 'c'); + $machine->transit('t', new StringAdapterContext('c')); self::assertSame(2, $i); } } diff --git a/tests/StateMachineTest.php b/tests/StateMachineTest.php index 5feb8b0..e266ff9 100644 --- a/tests/StateMachineTest.php +++ b/tests/StateMachineTest.php @@ -3,11 +3,11 @@ namespace Star\Component\State; use PHPUnit\Framework\TestCase; -use Star\Component\State\Callbacks\BufferStateChanges; -use Star\Component\State\Callbacks\TransitionCallback; 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\HardCodedTestContext; use Star\Component\State\Context\TestStubContext; use Star\Component\State\Event\StateEventStore; use Star\Component\State\Event\TransitionWasFailed; @@ -22,13 +22,13 @@ final class StateMachineTest extends TestCase { private TransitionRegistry $registry; private StateMachine $machine; - private TestContext $context; + private HardCodedTestContext $context; private EventRegistrySpy $events; public function setUp(): void { $this->events = new EventRegistrySpy(); - $this->context = new TestContext(); + $this->context = new HardCodedTestContext(); $this->registry = new TransitionRegistry(); $this->machine = new StateMachine('current', $this->registry, $this->events); } @@ -79,7 +79,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 ObjectAdapterContext(new stdClass, false)); + $this->machine->transit('t', new ObjectAdapterContext(new stdClass)); } public function test_it_should_throw_exception_with_context_as_string_when_transition_not_allowed(): void @@ -103,9 +103,7 @@ public function test_state_can_have_attribute(): void public function test_it_should_visit_the_transitions(): void { - $visitor = $this->createStub(TransitionVisitor::class); $registry = $this->createMock(StateRegistry::class); - $machine = new StateMachine('', $registry, $this->events); $visitor = $this->createStub(TransitionVisitor::class); $registry @@ -129,7 +127,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', new TestContext()); + $this->machine->transit('t', new HardCodedTestContext()); $this->fail('An exception should have been thrown'); } catch (Throwable $exception) { // silence it @@ -154,7 +152,7 @@ public function test_it_should_invoke_before_state_change_callback(): void $this->machine->transit( 't', - 'context', + new HardCodedTestContext(), $buffer, ); diff --git a/tests/StateMetadataTest.php b/tests/StateMetadataTest.php index 80a9515..e3730bb 100644 --- a/tests/StateMetadataTest.php +++ b/tests/StateMetadataTest.php @@ -3,8 +3,10 @@ namespace Star\Component\State; use PHPUnit\Framework\TestCase; +use RuntimeException; use Star\Component\State\Builder\StateBuilder; use Star\Component\State\Callbacks\NullCallback; +use Star\Component\State\Context\HardCodedTestContext; use Star\Component\State\Context\TestStubContext; final class StateMetadataTest extends TestCase @@ -25,7 +27,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', new TestStubContext('context')); + $new = $metadata->transit('t1', new HardCodedTestContext()); self::assertTrue($new->isInState('to')); } @@ -33,13 +35,13 @@ public function test_it_should_transit(): void public function test_it_should_use_the_failure_callback_on_transit(): void { $metadata = new CustomMetadata('to'); - $this->expectException(\RuntimeException::class); + $this->expectException(RuntimeException::class); $this->expectExceptionMessage( 'Method Star\Component\State\Callbacks\NullCallback::onFailure should never be called.' ); $metadata->transit( 't1', - new TestStubContext('context'), + new HardCodedTestContext(), new NullCallback() ); } diff --git a/tests/TestContext.php b/tests/TestContext.php deleted file mode 100644 index 7b338f0..0000000 --- a/tests/TestContext.php +++ /dev/null @@ -1,15 +0,0 @@ -