From 40d07bd1c58a07bf7dda68622b70b39e4efc8b4e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Sep 2020 18:06:40 +0200 Subject: [PATCH 001/145] Enable "native_constant_invocation" CS rule --- Registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Registry.php b/Registry.php index a2e7b8b..1094703 100644 --- a/Registry.php +++ b/Registry.php @@ -29,7 +29,7 @@ class Registry public function add(Workflow $workflow, $supportStrategy) { if (!$supportStrategy instanceof SupportStrategyInterface) { - @trigger_error('Support of class name string was deprecated after version 3.2 and won\'t work anymore in 4.0.', E_USER_DEPRECATED); + @trigger_error('Support of class name string was deprecated after version 3.2 and won\'t work anymore in 4.0.', \E_USER_DEPRECATED); $supportStrategy = new ClassInstanceSupportStrategy($supportStrategy); } From fa8bd34f253a80e39e9b784f6aaf7450a5c8cb1f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 6 Oct 2020 13:13:36 +0200 Subject: [PATCH 002/145] Remove "branch-alias", populate "version" --- composer.json | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 09e173a..a5e4e70 100644 --- a/composer.json +++ b/composer.json @@ -35,9 +35,5 @@ "psr-4": { "Symfony\\Component\\Workflow\\": "" } }, "minimum-stability": "dev", - "extra": { - "branch-alias": { - "dev-master": "3.4-dev" - } - } + "version": "3.4-dev" } From 9c6fb21ed1f7786d21efab9147e98c49d08bca22 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 6 Oct 2020 16:37:11 +0200 Subject: [PATCH 003/145] Update versions in composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index a5e4e70..f77b271 100644 --- a/composer.json +++ b/composer.json @@ -35,5 +35,5 @@ "psr-4": { "Symfony\\Component\\Workflow\\": "" } }, "minimum-stability": "dev", - "version": "3.4-dev" + "version": "3.4.x-dev" } From 5234eca5cef6501f8620eadfd7f842ad57cd5fe5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 7 Oct 2020 14:58:11 +0200 Subject: [PATCH 004/145] Remove "version" from composer.json files, use "branch-version" instead --- composer.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f77b271..80c88df 100644 --- a/composer.json +++ b/composer.json @@ -35,5 +35,7 @@ "psr-4": { "Symfony\\Component\\Workflow\\": "" } }, "minimum-stability": "dev", - "version": "3.4.x-dev" + "extra": { + "branch-version": "3.4-dev" + } } From 2c3b77fa3e29b67f18e07d8c739dc43a628bf63a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 13 Oct 2020 15:20:16 +0200 Subject: [PATCH 005/145] Fix branch-version --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 80c88df..4c1e255 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,6 @@ }, "minimum-stability": "dev", "extra": { - "branch-version": "3.4-dev" + "branch-version": "3.4" } } From fdd7bd981dd41a9ae0d9fdf6597e07cee0cf3e4a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 24 Oct 2020 12:23:57 +0200 Subject: [PATCH 006/145] Remove branch-version (keep them for contracts only) --- composer.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 4c1e255..510d19f 100644 --- a/composer.json +++ b/composer.json @@ -34,8 +34,5 @@ "autoload": { "psr-4": { "Symfony\\Component\\Workflow\\": "" } }, - "minimum-stability": "dev", - "extra": { - "branch-version": "3.4" - } + "minimum-stability": "dev" } From f636683c6aecdbed00a6ba7f46dbcb93e6eaaeb7 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Oct 2020 08:52:32 +0100 Subject: [PATCH 007/145] Use short array deconstruction syntax. --- Registry.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Registry.php b/Registry.php index 2363b80..f556c92 100644 --- a/Registry.php +++ b/Registry.php @@ -49,7 +49,7 @@ public function get($subject, $workflowName = null) { $matched = []; - foreach ($this->workflows as list($workflow, $supportStrategy)) { + foreach ($this->workflows as [$workflow, $supportStrategy]) { if ($this->supports($workflow, $supportStrategy, $subject, $workflowName)) { $matched[] = $workflow; } @@ -78,7 +78,7 @@ public function get($subject, $workflowName = null) public function all($subject): array { $matched = []; - foreach ($this->workflows as list($workflow, $supportStrategy)) { + foreach ($this->workflows as [$workflow, $supportStrategy]) { if ($supportStrategy->supports($workflow, $subject)) { $matched[] = $workflow; } From 092d0d1e4e093f621a4a1005a478fa079a437709 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 28 Oct 2020 22:33:29 +0100 Subject: [PATCH 008/145] Fix CS --- Registry.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Registry.php b/Registry.php index 6fef168..3474e95 100644 --- a/Registry.php +++ b/Registry.php @@ -29,7 +29,7 @@ public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategy public function has(object $subject, string $workflowName = null): bool { - foreach ($this->workflows as list($workflow, $supportStrategy)) { + foreach ($this->workflows as [$workflow, $supportStrategy]) { if ($this->supports($workflow, $supportStrategy, $subject, $workflowName)) { return true; } From c7ec9125ce467e49f1858762d3e2cc0d1dbd651e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 8 Dec 2020 17:59:59 +0100 Subject: [PATCH 009/145] Apply "visibility_required" CS rule to constants php-cs-fixer fix --rules='{"visibility_required": ["property", "method", "const"]}' --- Dumper/PlantUmlDumper.php | 8 ++++---- TransitionBlocker.php | 6 +++--- WorkflowEvents.php | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 48cd20d..76273c0 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -31,10 +31,10 @@ class PlantUmlDumper implements DumperInterface private const INITIAL = '<>'; private const MARKED = '<>'; - const STATEMACHINE_TRANSITION = 'arrow'; - const WORKFLOW_TRANSITION = 'square'; - const TRANSITION_TYPES = [self::STATEMACHINE_TRANSITION, self::WORKFLOW_TRANSITION]; - const DEFAULT_OPTIONS = [ + public const STATEMACHINE_TRANSITION = 'arrow'; + public const WORKFLOW_TRANSITION = 'square'; + public const TRANSITION_TYPES = [self::STATEMACHINE_TRANSITION, self::WORKFLOW_TRANSITION]; + public const DEFAULT_OPTIONS = [ 'skinparams' => [ 'titleBorderRoundCorner' => 15, 'titleBorderThickness' => 2, diff --git a/TransitionBlocker.php b/TransitionBlocker.php index 81fa1a4..1cd6459 100644 --- a/TransitionBlocker.php +++ b/TransitionBlocker.php @@ -16,9 +16,9 @@ */ final class TransitionBlocker { - const BLOCKED_BY_MARKING = '19beefc8-6b1e-4716-9d07-a39bd6d16e34'; - const BLOCKED_BY_EXPRESSION_GUARD_LISTENER = '326a1e9c-0c12-11e8-ba89-0ed5f89f718b'; - const UNKNOWN = 'e8b5bbb9-5913-4b98-bfa6-65dbd228a82a'; + public const BLOCKED_BY_MARKING = '19beefc8-6b1e-4716-9d07-a39bd6d16e34'; + public const BLOCKED_BY_EXPRESSION_GUARD_LISTENER = '326a1e9c-0c12-11e8-ba89-0ed5f89f718b'; + public const UNKNOWN = 'e8b5bbb9-5913-4b98-bfa6-65dbd228a82a'; private $message; private $code; diff --git a/WorkflowEvents.php b/WorkflowEvents.php index 44678dc..c84cda6 100644 --- a/WorkflowEvents.php +++ b/WorkflowEvents.php @@ -20,37 +20,37 @@ final class WorkflowEvents /** * @Event("Symfony\Component\Workflow\Event\GuardEvent") */ - const GUARD = 'workflow.guard'; + public const GUARD = 'workflow.guard'; /** * @Event("Symfony\Component\Workflow\Event\AnnounceEvent") */ - const ANNOUNCE = 'workflow.announce'; + public const ANNOUNCE = 'workflow.announce'; /** * @Event("Symfony\Component\Workflow\Event\CompletedEvent") */ - const COMPLETED = 'workflow.completed'; + public const COMPLETED = 'workflow.completed'; /** * @Event("Symfony\Component\Workflow\Event\EnterEvent") */ - const ENTER = 'workflow.enter'; + public const ENTER = 'workflow.enter'; /** * @Event("Symfony\Component\Workflow\Event\EnteredEvent") */ - const ENTERED = 'workflow.entered'; + public const ENTERED = 'workflow.entered'; /** * @Event("Symfony\Component\Workflow\Event\LeaveEvent") */ - const LEAVE = 'workflow.leave'; + public const LEAVE = 'workflow.leave'; /** * @Event("Symfony\Component\Workflow\Event\TransitionEvent") */ - const TRANSITION = 'workflow.transition'; + public const TRANSITION = 'workflow.transition'; private function __construct() { From 1309b9421324e3b0bc3ffbcb173e0ffb6af27a2b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 1 Jan 2021 10:24:35 +0100 Subject: [PATCH 010/145] Bump license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 684fbf9..c1f0aac 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2020 Fabien Potencier +Copyright (c) 2014-2021 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From bf878614acacacddf150ab3d8319c08eff7408ad Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 10 Jan 2021 09:16:05 +0100 Subject: [PATCH 011/145] Improve composer.json descriptions --- README.md | 3 +++ composer.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 068bd88..b0f092e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ Workflow Component =================== +The Workflow component provides tools for managing a workflow or finite state +machine. + Resources --------- diff --git a/composer.json b/composer.json index 132e8bf..941124d 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,7 @@ { "name": "symfony/workflow", "type": "library", - "description": "Symfony Workflow Component", + "description": "Provides tools for managing a workflow or finite state machine", "keywords": ["workflow", "petrinet", "place", "transition", "statemachine", "state"], "homepage": "https://symfony.com", "license": "MIT", From 8eea52a6078bc1f4695d151bfe76cd1dacc9cd3f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 11 Jan 2021 09:47:58 +0100 Subject: [PATCH 012/145] Use ::class keyword when possible --- Tests/DefinitionTest.php | 6 +++--- Tests/Metadata/InMemoryMetadataStoreTest.php | 2 +- Tests/RegistryTest.php | 4 ++-- Tests/Validator/StateMachineValidatorTest.php | 8 ++++---- Tests/Validator/WorkflowValidatorTest.php | 6 +++--- Tests/WorkflowTest.php | 10 +++++----- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index 6ba3c1e..da20a2b 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -36,7 +36,7 @@ public function testSetInitialPlaces() public function testSetInitialPlaceAndPlaceIsNotDefined() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('Place "d" cannot be the initial place as it does not exist.'); new Definition([], [], 'd'); } @@ -54,7 +54,7 @@ public function testAddTransition() public function testAddTransitionAndFromPlaceIsNotDefined() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); $places = range('a', 'b'); @@ -63,7 +63,7 @@ public function testAddTransitionAndFromPlaceIsNotDefined() public function testAddTransitionAndToPlaceIsNotDefined() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); $places = range('a', 'b'); diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index 5e5c8fe..6a62c7b 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -77,7 +77,7 @@ public function testGetMetadata() public function testGetMetadataWithUnknownType() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidArgumentException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Could not find a MetadataBag for the subject of type "boolean".'); $this->store->getMetadata('title', true); } diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index 7a02f31..26991dd 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -61,7 +61,7 @@ public function testGetWithSuccess() public function testGetWithMultipleMatch() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidArgumentException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Too many workflows (workflow2, workflow3) match this subject (Symfony\Component\Workflow\Tests\Subject2); set a different name on each and use the second (name) argument of this method.'); $w1 = $this->registry->get(new Subject2()); $this->assertInstanceOf(Workflow::class, $w1); @@ -70,7 +70,7 @@ public function testGetWithMultipleMatch() public function testGetWithNoMatch() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidArgumentException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Unable to find a workflow for class "stdClass".'); $w1 = $this->registry->get(new \stdClass()); $this->assertInstanceOf(Workflow::class, $w1); diff --git a/Tests/Validator/StateMachineValidatorTest.php b/Tests/Validator/StateMachineValidatorTest.php index f3014e8..b929a8c 100644 --- a/Tests/Validator/StateMachineValidatorTest.php +++ b/Tests/Validator/StateMachineValidatorTest.php @@ -11,7 +11,7 @@ class StateMachineValidatorTest extends TestCase { public function testWithMultipleTransitionWithSameNameShareInput() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('A transition from a place/state must have an unique name.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', 'a', 'b'); @@ -35,7 +35,7 @@ public function testWithMultipleTransitionWithSameNameShareInput() public function testWithMultipleTos() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('A transition in StateMachine can only have one output.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', 'a', ['b', 'c']); @@ -58,7 +58,7 @@ public function testWithMultipleTos() public function testWithMultipleFroms() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('A transition in StateMachine can only have one input.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', ['a', 'b'], 'c'); @@ -106,7 +106,7 @@ public function testValid() public function testWithTooManyInitialPlaces() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('The state machine "foo" can not store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index c67229c..12b9026 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -14,7 +14,7 @@ class WorkflowValidatorTest extends TestCase public function testSinglePlaceWorkflowValidatorAndComplexWorkflow() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('The marking store of workflow "foo" can not store many places.'); $definition = $this->createComplexWorkflowDefinition(); @@ -33,7 +33,7 @@ public function testSinglePlaceWorkflowValidatorAndSimpleWorkflow() public function testWorkflowWithInvalidNames() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); $places = range('a', 'c'); @@ -49,7 +49,7 @@ public function testWorkflowWithInvalidNames() public function testWithTooManyInitialPlaces() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('The marking store of workflow "foo" can not store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 54b2642..14e30be 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -22,7 +22,7 @@ class WorkflowTest extends TestCase public function testGetMarkingWithInvalidStoreReturn() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('The value returned by the MarkingStore is not an instance of "Symfony\Component\Workflow\Marking" for workflow "unnamed".'); $subject = new Subject(); $workflow = new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock()); @@ -32,7 +32,7 @@ public function testGetMarkingWithInvalidStoreReturn() public function testGetMarkingWithEmptyDefinition() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('The Marking is empty and there is no initial place for workflow "unnamed".'); $subject = new Subject(); $workflow = new Workflow(new Definition([], []), new MethodMarkingStore()); @@ -42,7 +42,7 @@ public function testGetMarkingWithEmptyDefinition() public function testGetMarkingWithImpossiblePlace() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('Place "nope" is not valid for workflow "unnamed".'); $subject = new Subject(); $subject->setMarking(['nope' => 1]); @@ -169,7 +169,7 @@ public function testCanWithSameNameTransition() public function testBuildTransitionBlockerListReturnsUndefinedTransition() { - $this->expectException('Symfony\Component\Workflow\Exception\UndefinedTransitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\UndefinedTransitionException::class); $this->expectExceptionMessage('Transition "404 Not Found" is not defined for workflow "unnamed".'); $definition = $this->createSimpleWorkflowDefinition(); $subject = new Subject(); @@ -249,7 +249,7 @@ public function testBuildTransitionBlockerListReturnsReasonsProvidedInGuards() public function testApplyWithNotExisingTransition() { - $this->expectException('Symfony\Component\Workflow\Exception\UndefinedTransitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\UndefinedTransitionException::class); $this->expectExceptionMessage('Transition "404 Not Found" is not defined for workflow "unnamed".'); $definition = $this->createComplexWorkflowDefinition(); $subject = new Subject(); From 6a37b0d3a8173cac52d73bbc67055aadfa4d1317 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 11 Jan 2021 11:11:08 +0100 Subject: [PATCH 013/145] Use ::class keyword when possible --- Tests/Metadata/InMemoryMetadataStoreTest.php | 2 +- Tests/Validator/WorkflowValidatorTest.php | 2 +- Tests/WorkflowTest.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index ba54bcd..14cc2c9 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -77,7 +77,7 @@ public function testGetMetadata() public function testGetMetadataWithUnknownType() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidArgumentException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); $this->expectExceptionMessage('Could not find a MetadataBag for the subject of type "bool".'); $this->store->getMetadata('title', true); } diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index 58f8ba7..038994c 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -14,7 +14,7 @@ class WorkflowValidatorTest extends TestCase public function testWorkflowWithInvalidNames() { - $this->expectException('Symfony\Component\Workflow\Exception\InvalidDefinitionException'); + $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); $places = range('a', 'c'); diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index e53ed36..884e202 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -23,7 +23,7 @@ class WorkflowTest extends TestCase public function testGetMarkingWithInvalidStoreReturn() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('The value returned by the MarkingStore is not an instance of "Symfony\Component\Workflow\Marking" for workflow "unnamed".'); $subject = new Subject(); $workflow = new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock()); @@ -33,7 +33,7 @@ public function testGetMarkingWithInvalidStoreReturn() public function testGetMarkingWithEmptyDefinition() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('The Marking is empty and there is no initial place for workflow "unnamed".'); $subject = new Subject(); $workflow = new Workflow(new Definition([], []), new MethodMarkingStore()); @@ -43,7 +43,7 @@ public function testGetMarkingWithEmptyDefinition() public function testGetMarkingWithImpossiblePlace() { - $this->expectException('Symfony\Component\Workflow\Exception\LogicException'); + $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); $this->expectExceptionMessage('Place "nope" is not valid for workflow "unnamed".'); $subject = new Subject(); $subject->setMarking(['nope' => 1]); @@ -170,7 +170,7 @@ public function testCanWithSameNameTransition() public function testBuildTransitionBlockerListReturnsUndefinedTransition() { - $this->expectException('Symfony\Component\Workflow\Exception\UndefinedTransitionException'); + $this->expectException(UndefinedTransitionException::class); $this->expectExceptionMessage('Transition "404 Not Found" is not defined for workflow "unnamed".'); $definition = $this->createSimpleWorkflowDefinition(); $subject = new Subject(); From 97bfe13f709dcd5f4468a9576d01e2cccd59aed2 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 22 Jan 2021 13:09:22 +0100 Subject: [PATCH 014/145] Use createMock() and use import instead of FQCN --- Tests/DefinitionTest.php | 7 ++++--- Tests/EventListener/GuardListenerTest.php | 10 +++++----- Tests/Metadata/InMemoryMetadataStoreTest.php | 3 ++- Tests/RegistryTest.php | 17 +++++++++-------- .../ClassInstanceSupportStrategyTest.php | 4 +--- Tests/Validator/StateMachineValidatorTest.php | 9 +++++---- Tests/Validator/WorkflowValidatorTest.php | 7 ++++--- Tests/WorkflowTest.php | 14 ++++++++------ 8 files changed, 38 insertions(+), 33 deletions(-) diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index da20a2b..ed6e7d3 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Exception\LogicException; use Symfony\Component\Workflow\Transition; class DefinitionTest extends TestCase @@ -36,7 +37,7 @@ public function testSetInitialPlaces() public function testSetInitialPlaceAndPlaceIsNotDefined() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "d" cannot be the initial place as it does not exist.'); new Definition([], [], 'd'); } @@ -54,7 +55,7 @@ public function testAddTransition() public function testAddTransitionAndFromPlaceIsNotDefined() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); $places = range('a', 'b'); @@ -63,7 +64,7 @@ public function testAddTransitionAndFromPlaceIsNotDefined() public function testAddTransitionAndToPlaceIsNotDefined() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); $places = range('a', 'b'); diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index 713cd45..4d6741a 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -39,11 +39,11 @@ protected function setUp(): void ]; $expressionLanguage = new ExpressionLanguage(); $token = new UsernamePasswordToken('username', 'credentials', 'provider', ['ROLE_USER']); - $tokenStorage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); + $tokenStorage = $this->createMock(TokenStorageInterface::class); $tokenStorage->expects($this->any())->method('getToken')->willReturn($token); - $this->authenticationChecker = $this->getMockBuilder(AuthorizationCheckerInterface::class)->getMock(); - $trustResolver = $this->getMockBuilder(AuthenticationTrustResolverInterface::class)->getMock(); - $this->validator = $this->getMockBuilder(ValidatorInterface::class)->getMock(); + $this->authenticationChecker = $this->createMock(AuthorizationCheckerInterface::class); + $trustResolver = $this->createMock(AuthenticationTrustResolverInterface::class); + $this->validator = $this->createMock(ValidatorInterface::class); $roleHierarchy = new RoleHierarchy([]); $this->listener = new GuardListener($this->configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, $roleHierarchy, $this->validator); } @@ -138,7 +138,7 @@ private function createEvent(Transition $transition = null) $subject = new Subject(); $transition = $transition ?: new Transition('name', 'from', 'to'); - $workflow = $this->getMockBuilder(WorkflowInterface::class)->getMock(); + $workflow = $this->createMock(WorkflowInterface::class); return new GuardEvent($subject, new Marking($subject->getMarking() ?? []), $transition, $workflow); } diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index 6a62c7b..b834a60 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -3,6 +3,7 @@ namespace Symfony\Component\Workflow\Tests\Metadata; use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Transition; @@ -77,7 +78,7 @@ public function testGetMetadata() public function testGetMetadataWithUnknownType() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Could not find a MetadataBag for the subject of type "boolean".'); $this->store->getMetadata('title', true); } diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index 26991dd..3ad7780 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\SupportStrategy\SupportStrategyInterface; @@ -19,9 +20,9 @@ protected function setUp(): void { $this->registry = new Registry(); - $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock(), 'workflow1'), $this->createWorkflowSupportStrategy(Subject1::class)); - $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock(), 'workflow2'), $this->createWorkflowSupportStrategy(Subject2::class)); - $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock(), 'workflow3'), $this->createWorkflowSupportStrategy(Subject2::class)); + $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class), $this->createMock(EventDispatcherInterface::class), 'workflow1'), $this->createWorkflowSupportStrategy(Subject1::class)); + $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class), $this->createMock(EventDispatcherInterface::class), 'workflow2'), $this->createWorkflowSupportStrategy(Subject2::class)); + $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class), $this->createMock(EventDispatcherInterface::class), 'workflow3'), $this->createWorkflowSupportStrategy(Subject2::class)); } protected function tearDown(): void @@ -37,7 +38,7 @@ public function testAddIsDeprecated() { $registry = new Registry(); - $registry->add($w = new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock(), $this->getMockBuilder(EventDispatcherInterface::class)->getMock(), 'workflow1'), $this->createSupportStrategy(Subject1::class)); + $registry->add($w = new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class), $this->createMock(EventDispatcherInterface::class), 'workflow1'), $this->createSupportStrategy(Subject1::class)); $workflow = $registry->get(new Subject1()); $this->assertInstanceOf(Workflow::class, $workflow); @@ -61,7 +62,7 @@ public function testGetWithSuccess() public function testGetWithMultipleMatch() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Too many workflows (workflow2, workflow3) match this subject (Symfony\Component\Workflow\Tests\Subject2); set a different name on each and use the second (name) argument of this method.'); $w1 = $this->registry->get(new Subject2()); $this->assertInstanceOf(Workflow::class, $w1); @@ -70,7 +71,7 @@ public function testGetWithMultipleMatch() public function testGetWithNoMatch() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Unable to find a workflow for class "stdClass".'); $w1 = $this->registry->get(new \stdClass()); $this->assertInstanceOf(Workflow::class, $w1); @@ -109,7 +110,7 @@ public function testAllWithNoMatch() */ private function createSupportStrategy($supportedClassName) { - $strategy = $this->getMockBuilder(SupportStrategyInterface::class)->getMock(); + $strategy = $this->createMock(SupportStrategyInterface::class); $strategy->expects($this->any())->method('supports') ->willReturnCallback(function ($workflow, $subject) use ($supportedClassName) { return $subject instanceof $supportedClassName; @@ -123,7 +124,7 @@ private function createSupportStrategy($supportedClassName) */ private function createWorkflowSupportStrategy($supportedClassName) { - $strategy = $this->getMockBuilder(WorkflowSupportStrategyInterface::class)->getMock(); + $strategy = $this->createMock(WorkflowSupportStrategyInterface::class); $strategy->expects($this->any())->method('supports') ->willReturnCallback(function ($workflow, $subject) use ($supportedClassName) { return $subject instanceof $supportedClassName; diff --git a/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php b/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php index e79a8a1..59238e7 100644 --- a/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php +++ b/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php @@ -30,8 +30,6 @@ public function testSupportsIfNotClassInstance() private function createWorkflow() { - return $this->getMockBuilder(Workflow::class) - ->disableOriginalConstructor() - ->getMock(); + return $this->createMock(Workflow::class); } } diff --git a/Tests/Validator/StateMachineValidatorTest.php b/Tests/Validator/StateMachineValidatorTest.php index b929a8c..357e544 100644 --- a/Tests/Validator/StateMachineValidatorTest.php +++ b/Tests/Validator/StateMachineValidatorTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Exception\InvalidDefinitionException; use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\Validator\StateMachineValidator; @@ -11,7 +12,7 @@ class StateMachineValidatorTest extends TestCase { public function testWithMultipleTransitionWithSameNameShareInput() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('A transition from a place/state must have an unique name.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', 'a', 'b'); @@ -35,7 +36,7 @@ public function testWithMultipleTransitionWithSameNameShareInput() public function testWithMultipleTos() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('A transition in StateMachine can only have one output.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', 'a', ['b', 'c']); @@ -58,7 +59,7 @@ public function testWithMultipleTos() public function testWithMultipleFroms() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('A transition in StateMachine can only have one input.'); $places = ['a', 'b', 'c']; $transitions[] = new Transition('t1', ['a', 'b'], 'c'); @@ -106,7 +107,7 @@ public function testValid() public function testWithTooManyInitialPlaces() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('The state machine "foo" can not store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index 12b9026..eda2d4e 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Exception\InvalidDefinitionException; use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\Validator\WorkflowValidator; @@ -14,7 +15,7 @@ class WorkflowValidatorTest extends TestCase public function testSinglePlaceWorkflowValidatorAndComplexWorkflow() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('The marking store of workflow "foo" can not store many places.'); $definition = $this->createComplexWorkflowDefinition(); @@ -33,7 +34,7 @@ public function testSinglePlaceWorkflowValidatorAndSimpleWorkflow() public function testWorkflowWithInvalidNames() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); $places = range('a', 'c'); @@ -49,7 +50,7 @@ public function testWorkflowWithInvalidNames() public function testWithTooManyInitialPlaces() { - $this->expectException(\Symfony\Component\Workflow\Exception\InvalidDefinitionException::class); + $this->expectException(InvalidDefinitionException::class); $this->expectExceptionMessage('The marking store of workflow "foo" can not store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 14e30be..7d688d0 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -8,7 +8,9 @@ use Symfony\Component\Workflow\Event\Event; use Symfony\Component\Workflow\Event\GuardEvent; use Symfony\Component\Workflow\Event\TransitionEvent; +use Symfony\Component\Workflow\Exception\LogicException; use Symfony\Component\Workflow\Exception\NotEnabledTransitionException; +use Symfony\Component\Workflow\Exception\UndefinedTransitionException; use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; @@ -22,17 +24,17 @@ class WorkflowTest extends TestCase public function testGetMarkingWithInvalidStoreReturn() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('The value returned by the MarkingStore is not an instance of "Symfony\Component\Workflow\Marking" for workflow "unnamed".'); $subject = new Subject(); - $workflow = new Workflow(new Definition([], []), $this->getMockBuilder(MarkingStoreInterface::class)->getMock()); + $workflow = new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class)); $workflow->getMarking($subject); } public function testGetMarkingWithEmptyDefinition() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('The Marking is empty and there is no initial place for workflow "unnamed".'); $subject = new Subject(); $workflow = new Workflow(new Definition([], []), new MethodMarkingStore()); @@ -42,7 +44,7 @@ public function testGetMarkingWithEmptyDefinition() public function testGetMarkingWithImpossiblePlace() { - $this->expectException(\Symfony\Component\Workflow\Exception\LogicException::class); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "nope" is not valid for workflow "unnamed".'); $subject = new Subject(); $subject->setMarking(['nope' => 1]); @@ -169,7 +171,7 @@ public function testCanWithSameNameTransition() public function testBuildTransitionBlockerListReturnsUndefinedTransition() { - $this->expectException(\Symfony\Component\Workflow\Exception\UndefinedTransitionException::class); + $this->expectException(UndefinedTransitionException::class); $this->expectExceptionMessage('Transition "404 Not Found" is not defined for workflow "unnamed".'); $definition = $this->createSimpleWorkflowDefinition(); $subject = new Subject(); @@ -249,7 +251,7 @@ public function testBuildTransitionBlockerListReturnsReasonsProvidedInGuards() public function testApplyWithNotExisingTransition() { - $this->expectException(\Symfony\Component\Workflow\Exception\UndefinedTransitionException::class); + $this->expectException(UndefinedTransitionException::class); $this->expectExceptionMessage('Transition "404 Not Found" is not defined for workflow "unnamed".'); $definition = $this->createComplexWorkflowDefinition(); $subject = new Subject(); From 077de4ab80186dc5fe553dd7d040e1e383624bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 4 Feb 2021 22:30:00 +0100 Subject: [PATCH 015/145] [Worflow] Fixed GuardListener when using the new Security system --- EventListener/GuardListener.php | 22 +++++++++++-------- .../InvalidTokenConfigurationException.php | 21 ------------------ 2 files changed, 13 insertions(+), 30 deletions(-) delete mode 100644 Exception/InvalidTokenConfigurationException.php diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 9329d40..8b63f93 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -17,7 +17,6 @@ use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\Workflow\Event\GuardEvent; -use Symfony\Component\Workflow\Exception\InvalidTokenConfigurationException; use Symfony\Component\Workflow\TransitionBlocker; /** @@ -76,15 +75,8 @@ private function getVariables(GuardEvent $event): array { $token = $this->tokenStorage->getToken(); - if (null === $token) { - throw new InvalidTokenConfigurationException(sprintf('There are no tokens available for workflow "%s".', $event->getWorkflowName())); - } - $variables = [ - 'token' => $token, - 'user' => $token->getUser(), 'subject' => $event->getSubject(), - 'role_names' => $this->roleHierarchy->getReachableRoleNames($token->getRoleNames()), // needed for the is_granted expression function 'auth_checker' => $this->authorizationChecker, // needed for the is_* expression function @@ -93,6 +85,18 @@ private function getVariables(GuardEvent $event): array 'validator' => $this->validator, ]; - return $variables; + if (null === $token) { + return $variables + [ + 'token' => null, + 'user' => null, + 'role_names' => [], + ]; + } + + return $variables + [ + 'token' => $token, + 'user' => $token->getUser(), + 'role_names' => $this->roleHierarchy->getReachableRoleNames($token->getRoleNames()), + ]; } } diff --git a/Exception/InvalidTokenConfigurationException.php b/Exception/InvalidTokenConfigurationException.php deleted file mode 100644 index a70fd4c..0000000 --- a/Exception/InvalidTokenConfigurationException.php +++ /dev/null @@ -1,21 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Workflow\Exception; - -/** - * Thrown by GuardListener when there is no token set, but guards are placed on a transition. - * - * @author Matt Johnson - */ -class InvalidTokenConfigurationException extends LogicException -{ -} From c3098279735abbd045b5519a0a84879bafce11ab Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 15 Feb 2021 15:36:09 +0100 Subject: [PATCH 016/145] [Workflow] Re-add InvalidTokenConfigurationException for BC --- .../InvalidTokenConfigurationException.php | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Exception/InvalidTokenConfigurationException.php diff --git a/Exception/InvalidTokenConfigurationException.php b/Exception/InvalidTokenConfigurationException.php new file mode 100644 index 0000000..a70fd4c --- /dev/null +++ b/Exception/InvalidTokenConfigurationException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Exception; + +/** + * Thrown by GuardListener when there is no token set, but guards are placed on a transition. + * + * @author Matt Johnson + */ +class InvalidTokenConfigurationException extends LogicException +{ +} From 798fe052a36b9174b09a4b893f39a81491a0a346 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Mon, 15 Feb 2021 15:46:52 +0100 Subject: [PATCH 017/145] [Workflow] Deprecate InvalidTokenConfigurationException --- CHANGELOG.md | 5 +++++ Exception/InvalidTokenConfigurationException.php | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d44a3b..553acdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.3 +--- + + * Deprecate `InvalidTokenConfigurationException` + 5.2.0 ----- diff --git a/Exception/InvalidTokenConfigurationException.php b/Exception/InvalidTokenConfigurationException.php index a70fd4c..b287b4a 100644 --- a/Exception/InvalidTokenConfigurationException.php +++ b/Exception/InvalidTokenConfigurationException.php @@ -11,10 +11,14 @@ namespace Symfony\Component\Workflow\Exception; +trigger_deprecation('symfony/workflow', '5.3', sprintf('The "%s" class is deprecated.', InvalidTokenConfigurationException::class)); + /** * Thrown by GuardListener when there is no token set, but guards are placed on a transition. * * @author Matt Johnson + * + * @deprecated since Symfony 5.3 */ class InvalidTokenConfigurationException extends LogicException { From 56d598c23839ce8ac74b9d7b211eb0092ba50c10 Mon Sep 17 00:00:00 2001 From: Robin Chalas Date: Wed, 10 Mar 2021 10:25:14 +0100 Subject: [PATCH 018/145] Don't use sprintf in trigger_deprecation() calls --- Exception/InvalidTokenConfigurationException.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Exception/InvalidTokenConfigurationException.php b/Exception/InvalidTokenConfigurationException.php index b287b4a..5d56fc5 100644 --- a/Exception/InvalidTokenConfigurationException.php +++ b/Exception/InvalidTokenConfigurationException.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Workflow\Exception; -trigger_deprecation('symfony/workflow', '5.3', sprintf('The "%s" class is deprecated.', InvalidTokenConfigurationException::class)); +trigger_deprecation('symfony/workflow', '5.3', 'The "%s" class is deprecated.', InvalidTokenConfigurationException::class); /** * Thrown by GuardListener when there is no token set, but guards are placed on a transition. From 238c0aa30f32feb0b86200a8e26e70149ff801f9 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 7 Apr 2021 17:14:41 +0200 Subject: [PATCH 019/145] [CS] Replace easy occurences of ?: with ?? --- Definition.php | 2 +- Metadata/InMemoryMetadataStore.php | 2 +- StateMachine.php | 2 +- Tests/EventListener/GuardListenerTest.php | 2 +- Workflow.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Definition.php b/Definition.php index e8fe71f..fe82274 100644 --- a/Definition.php +++ b/Definition.php @@ -44,7 +44,7 @@ public function __construct(array $places, array $transitions, $initialPlaces = $this->setInitialPlaces($initialPlaces); - $this->metadataStore = $metadataStore ?: new InMemoryMetadataStore(); + $this->metadataStore = $metadataStore ?? new InMemoryMetadataStore(); } /** diff --git a/Metadata/InMemoryMetadataStore.php b/Metadata/InMemoryMetadataStore.php index 5e8b9c4..a155388 100644 --- a/Metadata/InMemoryMetadataStore.php +++ b/Metadata/InMemoryMetadataStore.php @@ -28,7 +28,7 @@ public function __construct(array $workflowMetadata = [], array $placesMetadata { $this->workflowMetadata = $workflowMetadata; $this->placesMetadata = $placesMetadata; - $this->transitionsMetadata = $transitionsMetadata ?: new \SplObjectStorage(); + $this->transitionsMetadata = $transitionsMetadata ?? new \SplObjectStorage(); } public function getWorkflowMetadata(): array diff --git a/StateMachine.php b/StateMachine.php index 05f4c29..473477c 100644 --- a/StateMachine.php +++ b/StateMachine.php @@ -22,6 +22,6 @@ class StateMachine extends Workflow { public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') { - parent::__construct($definition, $markingStore ?: new MethodMarkingStore(true, 'marking'), $dispatcher, $name); + parent::__construct($definition, $markingStore ?? new MethodMarkingStore(true, 'marking'), $dispatcher, $name); } } diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index 4d6741a..bc6ec0d 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -136,7 +136,7 @@ public function testGuardExpressionBlocks() private function createEvent(Transition $transition = null) { $subject = new Subject(); - $transition = $transition ?: new Transition('name', 'from', 'to'); + $transition = $transition ?? new Transition('name', 'from', 'to'); $workflow = $this->createMock(WorkflowInterface::class); diff --git a/Workflow.php b/Workflow.php index 6b51b40..2fd62ea 100644 --- a/Workflow.php +++ b/Workflow.php @@ -42,7 +42,7 @@ class Workflow implements WorkflowInterface public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') { $this->definition = $definition; - $this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); + $this->markingStore = $markingStore ?? new MultipleStateMarkingStore(); if (null !== $dispatcher && class_exists(LegacyEventDispatcherProxy::class)) { $this->dispatcher = LegacyEventDispatcherProxy::decorate($dispatcher); From 1a6d5987c352f68e79130eea89517b2240b56c4b Mon Sep 17 00:00:00 2001 From: eFrane Date: Thu, 11 Feb 2021 20:42:56 +0100 Subject: [PATCH 020/145] [Workflow] Add Mermaid.js dumper --- CHANGELOG.md | 1 + Dumper/MermaidDumper.php | 288 +++++++++++++++++++++++++++++ Tests/Dumper/MermaidDumperTest.php | 226 ++++++++++++++++++++++ 3 files changed, 515 insertions(+) create mode 100644 Dumper/MermaidDumper.php create mode 100644 Tests/Dumper/MermaidDumperTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 553acdb..b62bacf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Deprecate `InvalidTokenConfigurationException` + * Added `MermaidDumper` to dump Workflow graphs in the Mermaid.js flowchart format 5.2.0 ----- diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php new file mode 100644 index 0000000..15ec8c6 --- /dev/null +++ b/Dumper/MermaidDumper.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Dumper; + +use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Exception\InvalidArgumentException; +use Symfony\Component\Workflow\Marking; + +class MermaidDumper implements DumperInterface +{ + public const DIRECTION_TOP_TO_BOTTOM = 'TB'; + public const DIRECTION_TOP_DOWN = 'TD'; + public const DIRECTION_BOTTOM_TO_TOP = 'BT'; + public const DIRECTION_RIGHT_TO_LEFT = 'RL'; + public const DIRECTION_LEFT_TO_RIGHT = 'LR'; + + private const VALID_DIRECTIONS = [ + self::DIRECTION_TOP_TO_BOTTOM, + self::DIRECTION_TOP_DOWN, + self::DIRECTION_BOTTOM_TO_TOP, + self::DIRECTION_RIGHT_TO_LEFT, + self::DIRECTION_LEFT_TO_RIGHT, + ]; + + public const TRANSITION_TYPE_STATEMACHINE = 'statemachine'; + public const TRANSITION_TYPE_WORKFLOW = 'workflow'; + + private const VALID_TRANSITION_TYPES = [ + self::TRANSITION_TYPE_STATEMACHINE, + self::TRANSITION_TYPE_WORKFLOW, + ]; + + /** + * @var string + */ + private $direction; + + /** + * @var string + */ + private $transitionType; + + /** + * Just tracking the transition id is in some cases inaccurate to + * get the link's number for styling purposes. + * + * @var int + */ + private $linkCount; + + public function __construct(string $transitionType, string $direction = self::DIRECTION_LEFT_TO_RIGHT) + { + $this->validateDirection($direction); + $this->validateTransitionType($transitionType); + + $this->direction = $direction; + $this->transitionType = $transitionType; + } + + public function dump(Definition $definition, Marking $marking = null, array $options = []): string + { + $this->linkCount = 0; + $placeNameMap = []; + $placeId = 0; + + $output = ['graph '.$this->direction]; + + $meta = $definition->getMetadataStore(); + + foreach ($definition->getPlaces() as $place) { + [$placeNode, $placeStyle] = $this->preparePlace( + $placeId, + $place, + $meta->getPlaceMetadata($place), + \in_array($place, $definition->getInitialPlaces()), + null !== $marking && $marking->has($place) + ); + + $output[] = $placeNode; + + if ('' !== $placeStyle) { + $output[] = $placeStyle; + } + + $placeNameMap[$place] = $place.$placeId; + + ++$placeId; + } + + foreach ($definition->getTransitions() as $transitionId => $transition) { + $transitionMeta = $meta->getTransitionMetadata($transition); + + $transitionLabel = $transition->getName(); + if (\array_key_exists('label', $transitionMeta)) { + $transitionLabel = $transitionMeta['label']; + } + + foreach ($transition->getFroms() as $from) { + $from = $placeNameMap[$from]; + + foreach ($transition->getTos() as $to) { + $to = $placeNameMap[$to]; + + if (self::TRANSITION_TYPE_STATEMACHINE === $this->transitionType) { + $transitionOutput = $this->styleStatemachineTransition( + $from, + $to, + $transitionId, + $transitionLabel, + $transitionMeta + ); + } else { + $transitionOutput = $this->styleWorkflowTransition( + $from, + $to, + $transitionId, + $transitionLabel, + $transitionMeta + ); + } + + foreach ($transitionOutput as $line) { + if (\in_array($line, $output)) { + // additional links must be decremented again to align the styling + if (0 < strpos($line, '-->')) { + --$this->linkCount; + } + + continue; + } + + $output[] = $line; + } + } + } + } + + return implode("\n", $output); + } + + private function preparePlace(int $placeId, string $placeName, array $meta, bool $isInitial, bool $hasMarking): array + { + $placeLabel = $placeName; + if (\array_key_exists('label', $meta)) { + $placeLabel = $meta['label']; + } + + $placeLabel = $this->escape($placeLabel); + + $labelShape = '((%s))'; + if ($isInitial) { + $labelShape = '([%s])'; + } + + $placeNodeName = $placeName.$placeId; + $placeNodeFormat = '%s'.$labelShape; + $placeNode = sprintf($placeNodeFormat, $placeNodeName, $placeLabel); + + $placeStyle = $this->styleNode($meta, $placeNodeName, $hasMarking); + + return [$placeNode, $placeStyle]; + } + + private function styleNode(array $meta, string $nodeName, bool $hasMarking = false): string + { + $nodeStyles = []; + + if (\array_key_exists('bg_color', $meta)) { + $nodeStyles[] = sprintf( + 'fill:%s', + $meta['bg_color'] + ); + } + + if ($hasMarking) { + $nodeStyles[] = 'stroke-width:4px'; + } + + if (0 === \count($nodeStyles)) { + return ''; + } + + return sprintf('style %s %s', $nodeName, implode(',', $nodeStyles)); + } + + /** + * Replace double quotes with the mermaid escape syntax and + * ensure all other characters are properly escaped. + */ + private function escape(string $label) + { + $label = str_replace('"', '#quot;', $label); + + return sprintf('"%s"', $label); + } + + public function validateDirection(string $direction): void + { + if (!\in_array($direction, self::VALID_DIRECTIONS, true)) { + throw new InvalidArgumentException(sprintf('Direction "%s" is not valid, valid directions are: "%s".', $direction, implode(', ', self::VALID_DIRECTIONS))); + } + } + + private function validateTransitionType(string $transitionType): void + { + if (!\in_array($transitionType, self::VALID_TRANSITION_TYPES, true)) { + throw new InvalidArgumentException(sprintf('Transition type "%s" is not valid, valid types are: "%s".', $transitionType, implode(', ', self::VALID_TRANSITION_TYPES))); + } + } + + private function styleStatemachineTransition( + string $from, + string $to, + int $transitionId, + string $transitionLabel, + array $transitionMeta + ): array { + $transitionOutput = [sprintf('%s-->|%s|%s', $from, $this->escape($transitionLabel), $to)]; + + $linkStyle = $this->styleLink($transitionMeta); + if ('' !== $linkStyle) { + $transitionOutput[] = $linkStyle; + } + + ++$this->linkCount; + + return $transitionOutput; + } + + private function styleWorkflowTransition( + string $from, + string $to, + int $transitionId, + string $transitionLabel, + array $transitionMeta + ) { + $transitionOutput = []; + + $transitionLabel = $this->escape($transitionLabel); + $transitionNodeName = 'transition'.$transitionId; + + $transitionOutput[] = sprintf('%s[%s]', $transitionNodeName, $transitionLabel); + + $transitionNodeStyle = $this->styleNode($transitionMeta, $transitionNodeName); + if ('' !== $transitionNodeStyle) { + $transitionOutput[] = $transitionNodeStyle; + } + + $connectionStyle = '%s-->%s'; + $transitionOutput[] = sprintf($connectionStyle, $from, $transitionNodeName); + + $linkStyle = $this->styleLink($transitionMeta); + if ('' !== $linkStyle) { + $transitionOutput[] = $linkStyle; + } + + ++$this->linkCount; + + $transitionOutput[] = sprintf($connectionStyle, $transitionNodeName, $to); + + $linkStyle = $this->styleLink($transitionMeta); + if ('' !== $linkStyle) { + $transitionOutput[] = $linkStyle; + } + + ++$this->linkCount; + + return $transitionOutput; + } + + private function styleLink(array $transitionMeta): string + { + if (\array_key_exists('color', $transitionMeta)) { + return sprintf('linkStyle %d stroke:%s', $this->linkCount, $transitionMeta['color']); + } + + return ''; + } +} diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php new file mode 100644 index 0000000..4eaebe1 --- /dev/null +++ b/Tests/Dumper/MermaidDumperTest.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\Dumper; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\DefinitionBuilder; +use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; +use Symfony\Component\Workflow\Transition; + +class MermaidDumperTest extends TestCase +{ + use WorkflowBuilderTrait; + + /** + * @dataProvider provideWorkflowDefinitionWithoutMarking + */ + public function testDumpWithoutMarking(Definition $definition, string $expected) + { + $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW); + + $dump = $dumper->dump($definition); + + $this->assertEquals($expected, $dump); + } + + /** + * @dataProvider provideWorkflowWithReservedWords + */ + public function testDumpWithReservedWordsAsPlacenames(Definition $definition, string $expected) + { + $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW); + + $dump = $dumper->dump($definition); + + $this->assertEquals($expected, $dump); + } + + /** + * @dataProvider provideStatemachine + */ + public function testDumpAsStatemachine(Definition $definition, string $expected) + { + $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_STATEMACHINE); + + $dump = $dumper->dump($definition); + + $this->assertEquals($expected, $dump); + } + + /** + * @dataProvider provideWorkflowWithMarking + */ + public function testDumpWorkflowWithMarking(Definition $definition, Marking $marking, string $expected) + { + $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW); + + $dump = $dumper->dump($definition, $marking); + + $this->assertEquals($expected, $dump); + } + + public function provideWorkflowDefinitionWithoutMarking(): array + { + return [ + [ + $this->createComplexWorkflowDefinition(), + "graph LR\n" + ."a0([\"a\"])\n" + ."b1((\"b\"))\n" + ."c2((\"c\"))\n" + ."d3((\"d\"))\n" + ."e4((\"e\"))\n" + ."f5((\"f\"))\n" + ."g6((\"g\"))\n" + ."transition0[\"t1\"]\n" + ."a0-->transition0\n" + ."transition0-->b1\n" + ."transition0-->c2\n" + ."transition1[\"t2\"]\n" + ."b1-->transition1\n" + ."transition1-->d3\n" + ."c2-->transition1\n" + ."transition2[\"My custom transition label 1\"]\n" + ."d3-->transition2\n" + ."linkStyle 6 stroke:Red\n" + ."transition2-->e4\n" + ."linkStyle 7 stroke:Red\n" + ."transition3[\"t4\"]\n" + ."d3-->transition3\n" + ."transition3-->f5\n" + ."transition4[\"t5\"]\n" + ."e4-->transition4\n" + ."transition4-->g6\n" + ."transition5[\"t6\"]\n" + ."f5-->transition5\n" + .'transition5-->g6', + ], + [ + $this->createWorkflowWithSameNameTransition(), + "graph LR\n" + ."a0([\"a\"])\n" + ."b1((\"b\"))\n" + ."c2((\"c\"))\n" + ."transition0[\"a_to_bc\"]\n" + ."a0-->transition0\n" + ."transition0-->b1\n" + ."transition0-->c2\n" + ."transition1[\"b_to_c\"]\n" + ."b1-->transition1\n" + ."transition1-->c2\n" + ."transition2[\"to_a\"]\n" + ."b1-->transition2\n" + ."transition2-->a0\n" + ."transition3[\"to_a\"]\n" + ."c2-->transition3\n" + .'transition3-->a0', + ], + [ + $this->createSimpleWorkflowDefinition(), + "graph LR\n" + ."a0([\"a\"])\n" + ."b1((\"b\"))\n" + ."c2((\"c\"))\n" + ."style c2 fill:DeepSkyBlue\n" + ."transition0[\"My custom transition label 2\"]\n" + ."a0-->transition0\n" + ."linkStyle 0 stroke:Grey\n" + ."transition0-->b1\n" + ."linkStyle 1 stroke:Grey\n" + ."transition1[\"t2\"]\n" + ."b1-->transition1\n" + .'transition1-->c2', + ], + ]; + } + + public function provideWorkflowWithReservedWords() + { + $builder = new DefinitionBuilder(); + + $builder->addPlaces(['start', 'subgraph', 'end', 'finis']); + $builder->addTransitions([ + new Transition('t0', ['start', 'subgraph'], ['end']), + new Transition('t1', ['end'], ['finis']), + ]); + + $definition = $builder->build(); + + return [ + [ + $definition, + "graph LR\n" + ."start0([\"start\"])\n" + ."subgraph1((\"subgraph\"))\n" + ."end2((\"end\"))\n" + ."finis3((\"finis\"))\n" + ."transition0[\"t0\"]\n" + ."start0-->transition0\n" + ."transition0-->end2\n" + ."subgraph1-->transition0\n" + ."transition1[\"t1\"]\n" + ."end2-->transition1\n" + .'transition1-->finis3', + ], + ]; + } + + public function provideStatemachine(): array + { + return [ + [ + $this->createComplexStateMachineDefinition(), + "graph LR\n" + ."a0([\"a\"])\n" + ."b1((\"b\"))\n" + ."c2((\"c\"))\n" + ."d3((\"d\"))\n" + ."a0-->|\"t1\"|b1\n" + ."d3-->|\"My custom transition label 3\"|b1\n" + ."linkStyle 1 stroke:Grey\n" + ."b1-->|\"t2\"|c2\n" + .'b1-->|"t3"|d3', + ], + ]; + } + + public function provideWorkflowWithMarking(): array + { + $marking = new Marking(); + $marking->mark('b'); + $marking->mark('c'); + + return [ + [ + $this->createSimpleWorkflowDefinition(), + $marking, + "graph LR\n" + ."a0([\"a\"])\n" + ."b1((\"b\"))\n" + ."style b1 stroke-width:4px\n" + ."c2((\"c\"))\n" + ."style c2 fill:DeepSkyBlue,stroke-width:4px\n" + ."transition0[\"My custom transition label 2\"]\n" + ."a0-->transition0\n" + ."linkStyle 0 stroke:Grey\n" + ."transition0-->b1\n" + ."linkStyle 1 stroke:Grey\n" + ."transition1[\"t2\"]\n" + ."b1-->transition1\n" + .'transition1-->c2', + ], + ]; + } +} From 65ad1e489b191ba9c6e691f3c1c8513284c9182e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 19 May 2021 15:18:37 +0200 Subject: [PATCH 021/145] Bump Symfony 6 to PHP 8 --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 8dd026d..9639ea8 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,7 @@ } ], "require": { - "php": ">=7.2.5", - "symfony/polyfill-php80": "^1.15" + "php": ">=8.0.2" }, "require-dev": { "psr/log": "~1.0", From 77b0e7f172d9bc598fb727db58961f89fd3015d0 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 19 May 2021 18:46:45 +0200 Subject: [PATCH 022/145] Allow Symfony 6 --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 8dd026d..361eae1 100644 --- a/composer.json +++ b/composer.json @@ -25,11 +25,11 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/dependency-injection": "^4.4|^5.0", - "symfony/event-dispatcher": "^4.4|^5.0", - "symfony/expression-language": "^4.4|^5.0", - "symfony/security-core": "^4.4|^5.0", - "symfony/validator": "^4.4|^5.0" + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/expression-language": "^4.4|^5.0|^6.0", + "symfony/security-core": "^4.4|^5.0|^6.0", + "symfony/validator": "^4.4|^5.0|^6.0" }, "conflict": { "symfony/event-dispatcher": "<4.4" From 817248e8fd349b2d5b357ae4f38d3256a6d72cf9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 May 2021 14:59:02 +0200 Subject: [PATCH 023/145] Bump symfony/* deps to ^5.4|^6.0 --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 7a32bfb..47aa411 100644 --- a/composer.json +++ b/composer.json @@ -24,14 +24,14 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/expression-language": "^4.4|^5.0|^6.0", - "symfony/security-core": "^4.4|^5.0|^6.0", - "symfony/validator": "^4.4|^5.0|^6.0" + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/expression-language": "^5.4|^6.0", + "symfony/security-core": "^5.4|^6.0", + "symfony/validator": "^5.4|^6.0" }, "conflict": { - "symfony/event-dispatcher": "<4.4" + "symfony/event-dispatcher": "<5.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Workflow\\": "" } From a37e3c105070b2f9cdf0ebf0226c0e8ff5430fd0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 26 May 2021 19:39:37 +0200 Subject: [PATCH 024/145] Fix CS in README files --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b0f092e..66fb601 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ machine. Resources --------- - * [Documentation](https://symfony.com/doc/current/components/workflow.html) - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) + * [Documentation](https://symfony.com/doc/current/components/workflow.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) From 0080aef2545b35ba80910296dc5565fef0d01887 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 28 May 2021 11:02:26 +0200 Subject: [PATCH 025/145] [Workflow] add types to all arguments --- Definition.php | 4 ++-- DefinitionBuilder.php | 2 +- Event/Event.php | 2 +- Metadata/GetMetadataTrait.php | 23 +++----------------- Metadata/MetadataStoreInterface.php | 2 +- Tests/Metadata/InMemoryMetadataStoreTest.php | 7 ------ Transition.php | 2 +- 7 files changed, 9 insertions(+), 33 deletions(-) diff --git a/Definition.php b/Definition.php index 1233538..e05704c 100644 --- a/Definition.php +++ b/Definition.php @@ -32,7 +32,7 @@ final class Definition * @param Transition[] $transitions * @param string|string[]|null $initialPlaces */ - public function __construct(array $places, array $transitions, $initialPlaces = null, MetadataStoreInterface $metadataStore = null) + public function __construct(array $places, array $transitions, string|array|null $initialPlaces = null, MetadataStoreInterface $metadataStore = null) { foreach ($places as $place) { $this->addPlace($place); @@ -76,7 +76,7 @@ public function getMetadataStore(): MetadataStoreInterface return $this->metadataStore; } - private function setInitialPlaces($places = null) + private function setInitialPlaces(string|array|null $places = null) { if (!$places) { return; diff --git a/DefinitionBuilder.php b/DefinitionBuilder.php index 19e9067..9cfdcae 100644 --- a/DefinitionBuilder.php +++ b/DefinitionBuilder.php @@ -65,7 +65,7 @@ public function clear() * * @return $this */ - public function setInitialPlaces($initialPlaces) + public function setInitialPlaces(string|array|null $initialPlaces) { $this->initialPlaces = $initialPlaces; diff --git a/Event/Event.php b/Event/Event.php index e1f448a..02d5661 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -63,7 +63,7 @@ public function getWorkflowName() return $this->workflow->getName(); } - public function getMetadata(string $key, $subject) + public function getMetadata(string $key, string|Transition|null $subject) { return $this->workflow->getMetadataStore()->getMetadata($key, $subject); } diff --git a/Metadata/GetMetadataTrait.php b/Metadata/GetMetadataTrait.php index 04cd52c..063f59f 100644 --- a/Metadata/GetMetadataTrait.php +++ b/Metadata/GetMetadataTrait.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Workflow\Metadata; -use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Transition; /** @@ -19,30 +18,14 @@ */ trait GetMetadataTrait { - public function getMetadata(string $key, $subject = null) + public function getMetadata(string $key, string|Transition|null $subject = null) { if (null === $subject) { return $this->getWorkflowMetadata()[$key] ?? null; } - if (\is_string($subject)) { - $metadataBag = $this->getPlaceMetadata($subject); - if (!$metadataBag) { - return null; - } + $metadataBag = \is_string($subject) ? $this->getPlaceMetadata($subject) : $this->getTransitionMetadata($subject); - return $metadataBag[$key] ?? null; - } - - if ($subject instanceof Transition) { - $metadataBag = $this->getTransitionMetadata($subject); - if (!$metadataBag) { - return null; - } - - return $metadataBag[$key] ?? null; - } - - throw new InvalidArgumentException(sprintf('Could not find a MetadataBag for the subject of type "%s".', get_debug_type($subject))); + return $metadataBag[$key] ?? null; } } diff --git a/Metadata/MetadataStoreInterface.php b/Metadata/MetadataStoreInterface.php index 4c4578a..69571f5 100644 --- a/Metadata/MetadataStoreInterface.php +++ b/Metadata/MetadataStoreInterface.php @@ -35,5 +35,5 @@ public function getTransitionMetadata(Transition $transition): array; * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata */ - public function getMetadata(string $key, $subject = null); + public function getMetadata(string $key, string|Transition|null $subject = null); } diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index e12f5f9..ada21c5 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -75,11 +75,4 @@ public function testGetMetadata() $this->assertNull($this->store->getMetadata('description', $this->transition)); $this->assertNull($this->store->getMetadata('description', new Transition('transition_2', [], []))); } - - public function testGetMetadataWithUnknownType() - { - $this->expectException(InvalidArgumentException::class); - $this->expectExceptionMessage('Could not find a MetadataBag for the subject of type "bool".'); - $this->store->getMetadata('title', true); - } } diff --git a/Transition.php b/Transition.php index f5a19b1..c52f6db 100644 --- a/Transition.php +++ b/Transition.php @@ -25,7 +25,7 @@ class Transition * @param string|string[] $froms * @param string|string[] $tos */ - public function __construct(string $name, $froms, $tos) + public function __construct(string $name, string|array $froms, string|array $tos) { $this->name = $name; $this->froms = (array) $froms; From 48b59b8ce7038b89b13471f2a06f8e55e8cfd713 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 28 May 2021 17:52:26 +0200 Subject: [PATCH 026/145] Add return type to __toString() --- Tests/MarkingStore/MethodMarkingStoreTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 155f285..9624457 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -89,7 +89,7 @@ public function __construct(string $markingValue) $this->markingValue = $markingValue; } - public function __toString() + public function __toString(): string { return $this->markingValue; } From dc11285858a68e484e1ce499465408d511f24860 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 2 Jun 2021 18:09:43 +0200 Subject: [PATCH 027/145] Update phpunit.xml.dist files for phpunit >= 9.3 --- phpunit.xml.dist | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cf444d5..15e5deb 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - - + + ./ - - ./Tests - ./vendor - - - + + + ./Tests + ./vendor + + From 3b39a91657311c36e8edca9c2d277c6699c6864f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 29 Jun 2021 12:31:08 +0200 Subject: [PATCH 028/145] Add more types --- Dumper/GraphvizDumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 56623b5..a8186a8 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -245,7 +245,7 @@ protected function dotize(string $id): string /** * @internal */ - protected function escape($value): string + protected function escape(string|bool $value): string { return \is_bool($value) ? ($value ? '1' : '0') : addslashes($value); } From aeb55ceb307f85ab99fbf2fdb4ee16b49d9b2281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 19 May 2021 18:54:16 +0200 Subject: [PATCH 029/145] [Workflown] Add support for getting updated context after a transition --- CHANGELOG.md | 5 +++++ Marking.php | 17 +++++++++++++++++ Tests/WorkflowTest.php | 23 ++++++++++++++++++++++- Workflow.php | 2 ++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62bacf..69b9096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +5.4 +--- + + * Add support for getting updated context after a transition + 5.3 --- diff --git a/Marking.php b/Marking.php index 974a040..4396c2f 100644 --- a/Marking.php +++ b/Marking.php @@ -19,6 +19,7 @@ class Marking { private $places = []; + private $context = null; /** * @param int[] $representation Keys are the place name and values should be 1 @@ -49,4 +50,20 @@ public function getPlaces() { return $this->places; } + + /** + * @internal + */ + public function setContext(array $context): void + { + $this->context = $context; + } + + /** + * Returns the context after the subject has transitioned. + */ + public function getContext(): ?array + { + return $this->context; + } } diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 9e4a486..6eca532 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -637,7 +637,28 @@ public function testEventContext() $dispatcher->addListener($eventName, $assertWorkflowContext); } - $workflow->apply($subject, 't1', $context); + $marking = $workflow->apply($subject, 't1', $context); + + $this->assertInstanceOf(Marking::class, $marking); + $this->assertSame($context, $marking->getContext()); + } + + public function testEventContextUpdated() + { + $definition = $this->createComplexWorkflowDefinition(); + $subject = new Subject(); + $dispatcher = new EventDispatcher(); + + $workflow = new Workflow($definition, new MethodMarkingStore(), $dispatcher); + + $dispatcher->addListener('workflow.transition', function (TransitionEvent $event) { + $event->setContext(['foo' => 'bar']); + }); + + $marking = $workflow->apply($subject, 't1', ['initial']); + + $this->assertInstanceOf(Marking::class, $marking); + $this->assertSame(['foo' => 'bar'], $marking->getContext()); } public function testEventDefaultInitialContext() diff --git a/Workflow.php b/Workflow.php index 5b45c1f..12a0bab 100644 --- a/Workflow.php +++ b/Workflow.php @@ -244,6 +244,8 @@ public function apply(object $subject, string $transitionName, array $context = $this->announce($subject, $transition, $marking, $context); } + $marking->setContext($context); + return $marking; } From add9423d96ab535c8d35d1c9e045ddfa69cddeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20B=C3=B6sing?= <2189546+boesing@users.noreply.github.com> Date: Tue, 13 Jul 2021 19:14:44 +0200 Subject: [PATCH 030/145] qa: add missing type-hints to workflow `Transition` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Maximilian Bösing <2189546+boesing@users.noreply.github.com> --- Transition.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Transition.php b/Transition.php index f5a19b1..b1dbcbe 100644 --- a/Transition.php +++ b/Transition.php @@ -32,16 +32,25 @@ public function __construct(string $name, $froms, $tos) $this->tos = (array) $tos; } + /** + * @return string + */ public function getName() { return $this->name; } + /** + * @return string[] + */ public function getFroms() { return $this->froms; } + /** + * @return string[] + */ public function getTos() { return $this->tos; From c52e78e6c12c13bd782b05f5d32fb6424f1348ad Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 18 Jul 2021 16:04:40 +0200 Subject: [PATCH 031/145] Indicate compatibility with psr/log 2 and 3 Signed-off-by: Alexander M. Turek --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 941124d..39fa83e 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "symfony/property-access": "^3.4|^4.3|^5.0" }, "require-dev": { - "psr/log": "~1.0", + "psr/log": "^1|^2|^3", "symfony/dependency-injection": "^3.4|^4.0|^5.0", "symfony/event-dispatcher": "^4.3", "symfony/expression-language": "^3.4|^4.0|^5.0", From 53b6200838be1c60314a2cfb623d4e91edc92256 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 21 Jul 2021 10:27:50 +0200 Subject: [PATCH 032/145] phpdoc fixes --- TransitionBlockerList.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TransitionBlockerList.php b/TransitionBlockerList.php index f7f4a63..6c443e6 100644 --- a/TransitionBlockerList.php +++ b/TransitionBlockerList.php @@ -61,7 +61,7 @@ public function isEmpty(): bool /** * {@inheritdoc} * - * @return \ArrayIterator|TransitionBlocker[] + * @return \Traversable */ public function getIterator(): \Traversable { From 4d237308b6293f87aef5be0843ee6bb70167e918 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Aug 2021 16:46:41 +0200 Subject: [PATCH 033/145] Add return types - batch 1/n --- DefinitionBuilder.php | 16 ++++++++-------- Dumper/DumperInterface.php | 2 +- Dumper/GraphvizDumper.php | 2 +- Dumper/StateMachineGraphvizDumper.php | 2 +- EventListener/AuditTrailListener.php | 2 +- MarkingStore/MarkingStoreInterface.php | 2 +- Registry.php | 2 +- Tests/WorkflowTest.php | 11 ----------- Transition.php | 6 +++--- Workflow.php | 14 +++++++------- WorkflowInterface.php | 14 +++++++------- 11 files changed, 31 insertions(+), 42 deletions(-) diff --git a/DefinitionBuilder.php b/DefinitionBuilder.php index 9cfdcae..b023e62 100644 --- a/DefinitionBuilder.php +++ b/DefinitionBuilder.php @@ -40,7 +40,7 @@ public function __construct(array $places = [], array $transitions = []) /** * @return Definition */ - public function build() + public function build(): Definition { return new Definition($this->places, $this->transitions, $this->initialPlaces, $this->metadataStore); } @@ -50,7 +50,7 @@ public function build() * * @return $this */ - public function clear() + public function clear(): static { $this->places = []; $this->transitions = []; @@ -65,7 +65,7 @@ public function clear() * * @return $this */ - public function setInitialPlaces(string|array|null $initialPlaces) + public function setInitialPlaces(string|array|null $initialPlaces): static { $this->initialPlaces = $initialPlaces; @@ -75,7 +75,7 @@ public function setInitialPlaces(string|array|null $initialPlaces) /** * @return $this */ - public function addPlace(string $place) + public function addPlace(string $place): static { if (!$this->places) { $this->initialPlaces = $place; @@ -91,7 +91,7 @@ public function addPlace(string $place) * * @return $this */ - public function addPlaces(array $places) + public function addPlaces(array $places): static { foreach ($places as $place) { $this->addPlace($place); @@ -105,7 +105,7 @@ public function addPlaces(array $places) * * @return $this */ - public function addTransitions(array $transitions) + public function addTransitions(array $transitions): static { foreach ($transitions as $transition) { $this->addTransition($transition); @@ -117,7 +117,7 @@ public function addTransitions(array $transitions) /** * @return $this */ - public function addTransition(Transition $transition) + public function addTransition(Transition $transition): static { $this->transitions[] = $transition; @@ -127,7 +127,7 @@ public function addTransition(Transition $transition) /** * @return $this */ - public function setMetadataStore(MetadataStoreInterface $metadataStore) + public function setMetadataStore(MetadataStoreInterface $metadataStore): static { $this->metadataStore = $metadataStore; diff --git a/Dumper/DumperInterface.php b/Dumper/DumperInterface.php index e1d8c7d..1b74611 100644 --- a/Dumper/DumperInterface.php +++ b/Dumper/DumperInterface.php @@ -27,5 +27,5 @@ interface DumperInterface * * @return string The representation of the workflow */ - public function dump(Definition $definition, Marking $marking = null, array $options = []); + public function dump(Definition $definition, Marking $marking = null, array $options = []): string; } diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index a8186a8..37dad66 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -44,7 +44,7 @@ class GraphvizDumper implements DumperInterface * * node: The default options for nodes (places + transitions) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []) + public function dump(Definition $definition, Marking $marking = null, array $options = []): string { $places = $this->findPlaces($definition, $marking); $transitions = $this->findTransitions($definition); diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index 4bd818d..95b19c4 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -27,7 +27,7 @@ class StateMachineGraphvizDumper extends GraphvizDumper * * node: The default options for nodes (places) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []) + public function dump(Definition $definition, Marking $marking = null, array $options = []): string { $places = $this->findPlaces($definition, $marking); $edges = $this->findEdges($definition); diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 95a9590..f7a8f66 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -46,7 +46,7 @@ public function onEnter(Event $event) } } - public static function getSubscribedEvents() + public static function getSubscribedEvents(): array { return [ 'workflow.leave' => ['onLeave'], diff --git a/MarkingStore/MarkingStoreInterface.php b/MarkingStore/MarkingStoreInterface.php index 2d8d6ad..a9a2de6 100644 --- a/MarkingStore/MarkingStoreInterface.php +++ b/MarkingStore/MarkingStoreInterface.php @@ -29,7 +29,7 @@ interface MarkingStoreInterface * * @return Marking The marking */ - public function getMarking(object $subject); + public function getMarking(object $subject): Marking; /** * Sets a Marking to a subject. diff --git a/Registry.php b/Registry.php index 3474e95..2753a8d 100644 --- a/Registry.php +++ b/Registry.php @@ -41,7 +41,7 @@ public function has(object $subject, string $workflowName = null): bool /** * @return Workflow */ - public function get(object $subject, string $workflowName = null) + public function get(object $subject, string $workflowName = null): Workflow { $matched = []; diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 6eca532..75885c0 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -12,7 +12,6 @@ use Symfony\Component\Workflow\Exception\NotEnabledTransitionException; use Symfony\Component\Workflow\Exception\UndefinedTransitionException; use Symfony\Component\Workflow\Marking; -use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlocker; @@ -23,16 +22,6 @@ class WorkflowTest extends TestCase { use WorkflowBuilderTrait; - public function testGetMarkingWithInvalidStoreReturn() - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('The value returned by the MarkingStore is not an instance of "Symfony\Component\Workflow\Marking" for workflow "unnamed".'); - $subject = new Subject(); - $workflow = new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class)); - - $workflow->getMarking($subject); - } - public function testGetMarkingWithEmptyDefinition() { $this->expectException(LogicException::class); diff --git a/Transition.php b/Transition.php index b6947fe..b8dc032 100644 --- a/Transition.php +++ b/Transition.php @@ -35,7 +35,7 @@ public function __construct(string $name, string|array $froms, string|array $tos /** * @return string */ - public function getName() + public function getName(): string { return $this->name; } @@ -43,7 +43,7 @@ public function getName() /** * @return string[] */ - public function getFroms() + public function getFroms(): array { return $this->froms; } @@ -51,7 +51,7 @@ public function getFroms() /** * @return string[] */ - public function getTos() + public function getTos(): array { return $this->tos; } diff --git a/Workflow.php b/Workflow.php index 12a0bab..fc6c467 100644 --- a/Workflow.php +++ b/Workflow.php @@ -79,7 +79,7 @@ public function __construct(Definition $definition, MarkingStoreInterface $marki /** * {@inheritdoc} */ - public function getMarking(object $subject, array $context = []) + public function getMarking(object $subject, array $context = []): Marking { $marking = $this->markingStore->getMarking($subject); @@ -125,7 +125,7 @@ public function getMarking(object $subject, array $context = []) /** * {@inheritdoc} */ - public function can(object $subject, string $transitionName) + public function can(object $subject, string $transitionName): bool { $transitions = $this->definition->getTransitions(); $marking = $this->getMarking($subject); @@ -184,7 +184,7 @@ public function buildTransitionBlockerList(object $subject, string $transitionNa /** * {@inheritdoc} */ - public function apply(object $subject, string $transitionName, array $context = []) + public function apply(object $subject, string $transitionName, array $context = []): Marking { $marking = $this->getMarking($subject, $context); @@ -252,7 +252,7 @@ public function apply(object $subject, string $transitionName, array $context = /** * {@inheritdoc} */ - public function getEnabledTransitions(object $subject) + public function getEnabledTransitions(object $subject): array { $enabledTransitions = []; $marking = $this->getMarking($subject); @@ -289,7 +289,7 @@ public function getEnabledTransition(object $subject, string $name): ?Transition /** * {@inheritdoc} */ - public function getName() + public function getName(): string { return $this->name; } @@ -297,7 +297,7 @@ public function getName() /** * {@inheritdoc} */ - public function getDefinition() + public function getDefinition(): Definition { return $this->definition; } @@ -305,7 +305,7 @@ public function getDefinition() /** * {@inheritdoc} */ - public function getMarkingStore() + public function getMarkingStore(): MarkingStoreInterface { return $this->markingStore; } diff --git a/WorkflowInterface.php b/WorkflowInterface.php index 39d9361..6dfeca1 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -27,14 +27,14 @@ interface WorkflowInterface * * @throws LogicException */ - public function getMarking(object $subject); + public function getMarking(object $subject): Marking; /** * Returns true if the transition is enabled. * * @return bool true if the transition is enabled */ - public function can(object $subject, string $transitionName); + public function can(object $subject, string $transitionName): bool; /** * Builds a TransitionBlockerList to know why a transition is blocked. @@ -48,29 +48,29 @@ public function buildTransitionBlockerList(object $subject, string $transitionNa * * @throws LogicException If the transition is not applicable */ - public function apply(object $subject, string $transitionName, array $context = []); + public function apply(object $subject, string $transitionName, array $context = []): Marking; /** * Returns all enabled transitions. * * @return Transition[] All enabled transitions */ - public function getEnabledTransitions(object $subject); + public function getEnabledTransitions(object $subject): array; /** * @return string */ - public function getName(); + public function getName(): string; /** * @return Definition */ - public function getDefinition(); + public function getDefinition(): Definition; /** * @return MarkingStoreInterface */ - public function getMarkingStore(); + public function getMarkingStore(): MarkingStoreInterface; public function getMetadataStore(): MetadataStoreInterface; } From 61a4311636491ea34a74848e55e80ff63be9989c Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 8 Aug 2021 12:41:46 +0200 Subject: [PATCH 034/145] [Security] Deprecate AnonymousToken, non-UserInterface users, and token credentials --- Tests/EventListener/GuardListenerTest.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index bc6ec0d..a0f6c4c 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -8,6 +8,8 @@ use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Security\Core\User\InMemoryUser; +use Symfony\Component\Security\Core\User\User; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -38,7 +40,11 @@ protected function setUp(): void ], ]; $expressionLanguage = new ExpressionLanguage(); - $token = new UsernamePasswordToken('username', 'credentials', 'provider', ['ROLE_USER']); + if (class_exists(InMemoryUser::class)) { + $token = new UsernamePasswordToken(new InMemoryUser('username', 'credentials', ['ROLE_USER']), 'provider', ['ROLE_USER']); + } else { + $token = new UsernamePasswordToken(new User('username', 'credentials', ['ROLE_USER']), null, 'provider', ['ROLE_USER']); + } $tokenStorage = $this->createMock(TokenStorageInterface::class); $tokenStorage->expects($this->any())->method('getToken')->willReturn($token); $this->authenticationChecker = $this->createMock(AuthorizationCheckerInterface::class); From ddf22a07c3bb8c62e3ec64131715008fa9146a50 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 16 Aug 2021 18:31:32 +0200 Subject: [PATCH 035/145] Run php-cs-fixer --- DefinitionBuilder.php | 3 --- Registry.php | 3 --- Tests/Metadata/InMemoryMetadataStoreTest.php | 1 - Transition.php | 3 --- WorkflowInterface.php | 9 --------- 5 files changed, 19 deletions(-) diff --git a/DefinitionBuilder.php b/DefinitionBuilder.php index b023e62..834546d 100644 --- a/DefinitionBuilder.php +++ b/DefinitionBuilder.php @@ -37,9 +37,6 @@ public function __construct(array $places = [], array $transitions = []) $this->addTransitions($transitions); } - /** - * @return Definition - */ public function build(): Definition { return new Definition($this->places, $this->transitions, $this->initialPlaces, $this->metadataStore); diff --git a/Registry.php b/Registry.php index 2753a8d..ceba28b 100644 --- a/Registry.php +++ b/Registry.php @@ -38,9 +38,6 @@ public function has(object $subject, string $workflowName = null): bool return false; } - /** - * @return Workflow - */ public function get(object $subject, string $workflowName = null): Workflow { $matched = []; diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index ada21c5..aae41bf 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -3,7 +3,6 @@ namespace Symfony\Component\Workflow\Tests\Metadata; use PHPUnit\Framework\TestCase; -use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Transition; diff --git a/Transition.php b/Transition.php index b8dc032..a8d4dda 100644 --- a/Transition.php +++ b/Transition.php @@ -32,9 +32,6 @@ public function __construct(string $name, string|array $froms, string|array $tos $this->tos = (array) $tos; } - /** - * @return string - */ public function getName(): string { return $this->name; diff --git a/WorkflowInterface.php b/WorkflowInterface.php index 6dfeca1..0a21ffb 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -57,19 +57,10 @@ public function apply(object $subject, string $transitionName, array $context = */ public function getEnabledTransitions(object $subject): array; - /** - * @return string - */ public function getName(): string; - /** - * @return Definition - */ public function getDefinition(): Definition; - /** - * @return MarkingStoreInterface - */ public function getMarkingStore(): MarkingStoreInterface; public function getMetadataStore(): MetadataStoreInterface; From 55a1afe4ce8651c04c002ff33614ed2f2eb1ac3b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 12 Aug 2021 15:32:19 +0200 Subject: [PATCH 036/145] Cleanup more `@return` annotations --- Dumper/DumperInterface.php | 2 +- MarkingStore/MarkingStoreInterface.php | 2 +- WorkflowInterface.php | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dumper/DumperInterface.php b/Dumper/DumperInterface.php index e1d8c7d..19f04b0 100644 --- a/Dumper/DumperInterface.php +++ b/Dumper/DumperInterface.php @@ -25,7 +25,7 @@ interface DumperInterface /** * Dumps a workflow definition. * - * @return string The representation of the workflow + * @return string */ public function dump(Definition $definition, Marking $marking = null, array $options = []); } diff --git a/MarkingStore/MarkingStoreInterface.php b/MarkingStore/MarkingStoreInterface.php index 2d8d6ad..f942db7 100644 --- a/MarkingStore/MarkingStoreInterface.php +++ b/MarkingStore/MarkingStoreInterface.php @@ -27,7 +27,7 @@ interface MarkingStoreInterface /** * Gets a Marking from a subject. * - * @return Marking The marking + * @return Marking */ public function getMarking(object $subject); diff --git a/WorkflowInterface.php b/WorkflowInterface.php index 39d9361..410b7f5 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -23,7 +23,7 @@ interface WorkflowInterface /** * Returns the object's Marking. * - * @return Marking The Marking + * @return Marking * * @throws LogicException */ @@ -32,7 +32,7 @@ public function getMarking(object $subject); /** * Returns true if the transition is enabled. * - * @return bool true if the transition is enabled + * @return bool */ public function can(object $subject, string $transitionName); @@ -44,7 +44,7 @@ public function buildTransitionBlockerList(object $subject, string $transitionNa /** * Fire a transition. * - * @return Marking The new Marking + * @return Marking * * @throws LogicException If the transition is not applicable */ @@ -53,7 +53,7 @@ public function apply(object $subject, string $transitionName, array $context = /** * Returns all enabled transitions. * - * @return Transition[] All enabled transitions + * @return Transition[] */ public function getEnabledTransitions(object $subject); From b32680de100b3d304995d8cd4c1faae56755d575 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Fri, 4 Jun 2021 20:15:48 +0200 Subject: [PATCH 037/145] [Security] Remove everything related to the deprecated authentication manager --- Tests/EventListener/GuardListenerTest.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index a0f6c4c..a767ae5 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -9,7 +9,6 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Symfony\Component\Security\Core\Role\RoleHierarchy; use Symfony\Component\Security\Core\User\InMemoryUser; -use Symfony\Component\Security\Core\User\User; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationList; use Symfony\Component\Validator\Validator\ValidatorInterface; @@ -40,11 +39,7 @@ protected function setUp(): void ], ]; $expressionLanguage = new ExpressionLanguage(); - if (class_exists(InMemoryUser::class)) { - $token = new UsernamePasswordToken(new InMemoryUser('username', 'credentials', ['ROLE_USER']), 'provider', ['ROLE_USER']); - } else { - $token = new UsernamePasswordToken(new User('username', 'credentials', ['ROLE_USER']), null, 'provider', ['ROLE_USER']); - } + $token = new UsernamePasswordToken(new InMemoryUser('username', 'credentials', ['ROLE_USER']), 'provider', ['ROLE_USER']); $tokenStorage = $this->createMock(TokenStorageInterface::class); $tokenStorage->expects($this->any())->method('getToken')->willReturn($token); $this->authenticationChecker = $this->createMock(AuthorizationCheckerInterface::class); From b69f31b208de6110633c8a484c6804273662da7f Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 25 Aug 2021 21:53:50 +0200 Subject: [PATCH 038/145] [Workflow] Add types to private properties Signed-off-by: Alexander M. Turek --- Definition.php | 8 ++++---- DefinitionBuilder.php | 8 ++++---- Dumper/MermaidDumper.php | 15 +++------------ Dumper/PlantUmlDumper.php | 4 ++-- Event/Event.php | 8 ++++---- Event/GuardEvent.php | 2 +- EventListener/AuditTrailListener.php | 2 +- EventListener/GuardExpression.php | 4 ++-- EventListener/GuardListener.php | 14 +++++++------- Exception/NotEnabledTransitionException.php | 2 +- Exception/TransitionException.php | 8 ++++---- Marking.php | 4 ++-- MarkingStore/MethodMarkingStore.php | 4 ++-- Metadata/InMemoryMetadataStore.php | 6 +++--- Registry.php | 2 +- SupportStrategy/InstanceOfSupportStrategy.php | 2 +- Transition.php | 6 +++--- TransitionBlocker.php | 6 +++--- TransitionBlockerList.php | 2 +- Validator/WorkflowValidator.php | 2 +- Workflow.php | 10 +++++----- 21 files changed, 55 insertions(+), 64 deletions(-) diff --git a/Definition.php b/Definition.php index 86ee3b1..45de623 100644 --- a/Definition.php +++ b/Definition.php @@ -22,10 +22,10 @@ */ final class Definition { - private $places = []; - private $transitions = []; - private $initialPlaces = []; - private $metadataStore; + private array $places = []; + private array $transitions = []; + private array $initialPlaces = []; + private MetadataStoreInterface $metadataStore; /** * @param string[] $places diff --git a/DefinitionBuilder.php b/DefinitionBuilder.php index 834546d..5b7a15b 100644 --- a/DefinitionBuilder.php +++ b/DefinitionBuilder.php @@ -22,10 +22,10 @@ */ class DefinitionBuilder { - private $places = []; - private $transitions = []; - private $initialPlaces; - private $metadataStore; + private array $places = []; + private array $transitions = []; + private string|array|null $initialPlaces = null; + private ?MetadataStoreInterface $metadataStore = null; /** * @param string[] $places diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 15ec8c6..67d296e 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -39,23 +39,14 @@ class MermaidDumper implements DumperInterface self::TRANSITION_TYPE_WORKFLOW, ]; - /** - * @var string - */ - private $direction; - - /** - * @var string - */ - private $transitionType; + private string $direction; + private string $transitionType; /** * Just tracking the transition id is in some cases inaccurate to * get the link's number for styling purposes. - * - * @var int */ - private $linkCount; + private int $linkCount = 0; public function __construct(string $transitionType, string $direction = self::DIRECTION_LEFT_TO_RIGHT) { diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 76273c0..1b9b5f5 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -52,9 +52,9 @@ class PlantUmlDumper implements DumperInterface ], ]; - private $transitionType = self::STATEMACHINE_TRANSITION; + private string $transitionType = self::STATEMACHINE_TRANSITION; - public function __construct(string $transitionType = null) + public function __construct(string $transitionType) { if (!\in_array($transitionType, self::TRANSITION_TYPES, true)) { throw new InvalidArgumentException("Transition type '$transitionType' does not exist."); diff --git a/Event/Event.php b/Event/Event.php index 02d5661..53cc3a8 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -24,10 +24,10 @@ class Event extends BaseEvent { protected $context; - private $subject; - private $marking; - private $transition; - private $workflow; + private object $subject; + private Marking $marking; + private ?Transition $transition; + private ?WorkflowInterface $workflow; public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) { diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index 317fe89..1967e25 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -23,7 +23,7 @@ */ final class GuardEvent extends Event { - private $transitionBlockerList; + private TransitionBlockerList $transitionBlockerList; /** * {@inheritdoc} diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index f7a8f66..8a7ea37 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -20,7 +20,7 @@ */ class AuditTrailListener implements EventSubscriberInterface { - private $logger; + private LoggerInterface $logger; public function __construct(LoggerInterface $logger) { diff --git a/EventListener/GuardExpression.php b/EventListener/GuardExpression.php index c7ac9ce..9fb1525 100644 --- a/EventListener/GuardExpression.php +++ b/EventListener/GuardExpression.php @@ -15,8 +15,8 @@ class GuardExpression { - private $transition; - private $expression; + private Transition $transition; + private string $expression; public function __construct(Transition $transition, string $expression) { diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 8b63f93..5c873d8 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -24,13 +24,13 @@ */ class GuardListener { - private $configuration; - private $expressionLanguage; - private $tokenStorage; - private $authorizationChecker; - private $trustResolver; - private $roleHierarchy; - private $validator; + private array $configuration; + private ExpressionLanguage $expressionLanguage; + private TokenStorageInterface $tokenStorage; + private AuthorizationCheckerInterface $authorizationChecker; + private AuthenticationTrustResolverInterface $trustResolver; + private ?RoleHierarchyInterface $roleHierarchy; + private ?ValidatorInterface $validator; public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) { diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index 1771234..4144caf 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -21,7 +21,7 @@ */ class NotEnabledTransitionException extends TransitionException { - private $transitionBlockerList; + private TransitionBlockerList $transitionBlockerList; public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, TransitionBlockerList $transitionBlockerList, array $context = []) { diff --git a/Exception/TransitionException.php b/Exception/TransitionException.php index 5e35725..d493e22 100644 --- a/Exception/TransitionException.php +++ b/Exception/TransitionException.php @@ -19,10 +19,10 @@ */ class TransitionException extends LogicException { - private $subject; - private $transitionName; - private $workflow; - private $context; + private object $subject; + private string $transitionName; + private WorkflowInterface $workflow; + private array $context; public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, string $message, array $context = []) { diff --git a/Marking.php b/Marking.php index 4396c2f..36deb2d 100644 --- a/Marking.php +++ b/Marking.php @@ -18,8 +18,8 @@ */ class Marking { - private $places = []; - private $context = null; + private array $places = []; + private ?array $context = null; /** * @param int[] $representation Keys are the place name and values should be 1 diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index ff6dde4..93c5302 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -29,8 +29,8 @@ */ final class MethodMarkingStore implements MarkingStoreInterface { - private $singleState; - private $property; + private bool $singleState; + private string $property; /** * @param string $property Used to determine methods to call diff --git a/Metadata/InMemoryMetadataStore.php b/Metadata/InMemoryMetadataStore.php index a155388..866b7c0 100644 --- a/Metadata/InMemoryMetadataStore.php +++ b/Metadata/InMemoryMetadataStore.php @@ -20,9 +20,9 @@ final class InMemoryMetadataStore implements MetadataStoreInterface { use GetMetadataTrait; - private $workflowMetadata; - private $placesMetadata; - private $transitionsMetadata; + private array $workflowMetadata; + private array $placesMetadata; + private \SplObjectStorage $transitionsMetadata; public function __construct(array $workflowMetadata = [], array $placesMetadata = [], \SplObjectStorage $transitionsMetadata = null) { diff --git a/Registry.php b/Registry.php index ceba28b..1fbc76b 100644 --- a/Registry.php +++ b/Registry.php @@ -20,7 +20,7 @@ */ class Registry { - private $workflows = []; + private array $workflows = []; public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategyInterface $supportStrategy) { diff --git a/SupportStrategy/InstanceOfSupportStrategy.php b/SupportStrategy/InstanceOfSupportStrategy.php index 487795e..a00aba5 100644 --- a/SupportStrategy/InstanceOfSupportStrategy.php +++ b/SupportStrategy/InstanceOfSupportStrategy.php @@ -19,7 +19,7 @@ */ final class InstanceOfSupportStrategy implements WorkflowSupportStrategyInterface { - private $className; + private string $className; public function __construct(string $className) { diff --git a/Transition.php b/Transition.php index a8d4dda..50d834b 100644 --- a/Transition.php +++ b/Transition.php @@ -17,9 +17,9 @@ */ class Transition { - private $name; - private $froms; - private $tos; + private string $name; + private array $froms; + private array $tos; /** * @param string|string[] $froms diff --git a/TransitionBlocker.php b/TransitionBlocker.php index 9e52cc9..59a1ade 100644 --- a/TransitionBlocker.php +++ b/TransitionBlocker.php @@ -20,9 +20,9 @@ final class TransitionBlocker public const BLOCKED_BY_EXPRESSION_GUARD_LISTENER = '326a1e9c-0c12-11e8-ba89-0ed5f89f718b'; public const UNKNOWN = 'e8b5bbb9-5913-4b98-bfa6-65dbd228a82a'; - private $message; - private $code; - private $parameters; + private string $message; + private string $code; + private array $parameters; /** * @param string $code Code is a machine-readable string, usually an UUID diff --git a/TransitionBlockerList.php b/TransitionBlockerList.php index 6c443e6..2f317ea 100644 --- a/TransitionBlockerList.php +++ b/TransitionBlockerList.php @@ -18,7 +18,7 @@ */ final class TransitionBlockerList implements \IteratorAggregate, \Countable { - private $blockers; + private array $blockers; /** * @param TransitionBlocker[] $blockers diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index f0e7402..2ee8d60 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -20,7 +20,7 @@ */ class WorkflowValidator implements DefinitionValidatorInterface { - private $singlePlace; + private bool $singlePlace; public function __construct(bool $singlePlace = false) { diff --git a/Workflow.php b/Workflow.php index fc6c467..8682015 100644 --- a/Workflow.php +++ b/Workflow.php @@ -52,10 +52,10 @@ class Workflow implements WorkflowInterface WorkflowEvents::ANNOUNCE => self::DISABLE_ANNOUNCE_EVENT, ]; - private $definition; - private $markingStore; - private $dispatcher; - private $name; + private Definition $definition; + private MarkingStoreInterface $markingStore; + private ?EventDispatcherInterface $dispatcher; + private string $name; /** * When `null` fire all events (the default behaviour). @@ -65,7 +65,7 @@ class Workflow implements WorkflowInterface * * @var array|string[]|null */ - private $eventsToDispatch = null; + private ?array $eventsToDispatch = null; public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) { From e7987a704757051138139d0905f7c5f74d0e3343 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 7 Sep 2021 15:39:06 +0200 Subject: [PATCH 039/145] Remove deprecated code paths --- CHANGELOG.md | 5 ++++ .../InvalidTokenConfigurationException.php | 25 ------------------- 2 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 Exception/InvalidTokenConfigurationException.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b9096..b533107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.0 +--- + + * Remove `InvalidTokenConfigurationException` + 5.4 --- diff --git a/Exception/InvalidTokenConfigurationException.php b/Exception/InvalidTokenConfigurationException.php deleted file mode 100644 index 5d56fc5..0000000 --- a/Exception/InvalidTokenConfigurationException.php +++ /dev/null @@ -1,25 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Workflow\Exception; - -trigger_deprecation('symfony/workflow', '5.3', 'The "%s" class is deprecated.', InvalidTokenConfigurationException::class); - -/** - * Thrown by GuardListener when there is no token set, but guards are placed on a transition. - * - * @author Matt Johnson - * - * @deprecated since Symfony 5.3 - */ -class InvalidTokenConfigurationException extends LogicException -{ -} From 65768ab57480bc911feb95129dc84ec339318cfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Vo=C5=99=C3=AD=C5=A1ek?= Date: Sun, 3 Oct 2021 17:40:55 +0200 Subject: [PATCH 040/145] Fix "can not" spelling --- Tests/Validator/StateMachineValidatorTest.php | 2 +- Validator/StateMachineValidator.php | 2 +- Validator/WorkflowValidator.php | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Tests/Validator/StateMachineValidatorTest.php b/Tests/Validator/StateMachineValidatorTest.php index 357e544..5a92a04 100644 --- a/Tests/Validator/StateMachineValidatorTest.php +++ b/Tests/Validator/StateMachineValidatorTest.php @@ -108,7 +108,7 @@ public function testValid() public function testWithTooManyInitialPlaces() { $this->expectException(InvalidDefinitionException::class); - $this->expectExceptionMessage('The state machine "foo" can not store many places. But the definition has 2 initial places. Only one is supported.'); + $this->expectExceptionMessage('The state machine "foo" cannot store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; $definition = new Definition($places, $transitions, ['a', 'b']); diff --git a/Validator/StateMachineValidator.php b/Validator/StateMachineValidator.php index 355c760..d4e78ca 100644 --- a/Validator/StateMachineValidator.php +++ b/Validator/StateMachineValidator.php @@ -45,7 +45,7 @@ public function validate(Definition $definition, string $name) $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The state machine "%s" can not store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(sprintf('The state machine "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index f0e7402..53da407 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -46,13 +46,13 @@ public function validate(Definition $definition, string $name) foreach ($definition->getTransitions() as $transition) { if (1 < \count($transition->getTos())) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" can not store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); + throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); } } $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" can not store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } From 29a21ce240e62752a45633b8bbe8bbe2df02fef8 Mon Sep 17 00:00:00 2001 From: Toby Griffiths Date: Mon, 11 Oct 2021 13:46:14 +0100 Subject: [PATCH 041/145] GuardEvent::getTransition() cannot return null Co-authored-by: Alexander M. Turek Signed-off-by: Alexander M. Turek --- Event/GuardEvent.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index 317fe89..039d161 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -35,6 +35,11 @@ public function __construct(object $subject, Marking $marking, Transition $trans $this->transitionBlockerList = new TransitionBlockerList(); } + public function getTransition(): Transition + { + return parent::getTransition(); + } + public function isBlocked(): bool { return !$this->transitionBlockerList->isEmpty(); From f68f6f51a3433343a7f730689b54ea9caee176f4 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 3 Nov 2021 10:24:47 +0100 Subject: [PATCH 042/145] Add generic types to traversable implementations --- Metadata/InMemoryMetadataStore.php | 3 +++ TransitionBlockerList.php | 7 ++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Metadata/InMemoryMetadataStore.php b/Metadata/InMemoryMetadataStore.php index a155388..8fdc9e7 100644 --- a/Metadata/InMemoryMetadataStore.php +++ b/Metadata/InMemoryMetadataStore.php @@ -24,6 +24,9 @@ final class InMemoryMetadataStore implements MetadataStoreInterface private $placesMetadata; private $transitionsMetadata; + /** + * @param \SplObjectStorage|null $transitionsMetadata + */ public function __construct(array $workflowMetadata = [], array $placesMetadata = [], \SplObjectStorage $transitionsMetadata = null) { $this->workflowMetadata = $workflowMetadata; diff --git a/TransitionBlockerList.php b/TransitionBlockerList.php index 6c443e6..b87222c 100644 --- a/TransitionBlockerList.php +++ b/TransitionBlockerList.php @@ -15,6 +15,8 @@ * A list of transition blockers. * * @author Grégoire Pineau + * + * @implements \IteratorAggregate */ final class TransitionBlockerList implements \IteratorAggregate, \Countable { @@ -58,11 +60,6 @@ public function isEmpty(): bool return !$this->blockers; } - /** - * {@inheritdoc} - * - * @return \Traversable - */ public function getIterator(): \Traversable { return new \ArrayIterator($this->blockers); From d59ff33174ca777696e865745f2fddb4f940d8bb Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Mon, 6 Dec 2021 21:02:18 +0100 Subject: [PATCH 043/145] Use str_ends_with() and str_starts_with() --- Dumper/PlantUmlDumper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 1b9b5f5..f6b2536 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -237,7 +237,7 @@ private function getTransitionEscapedWithStyle(MetadataStoreInterface $workflowM private function getTransitionColor(string $color): string { // PUML format requires that color in transition have to be prefixed with “#”. - if ('#' !== substr($color, 0, 1)) { + if (!str_starts_with($color, '#')) { $color = '#'.$color; } From 084366d589ef6fcc725a79bfe70a9c92e421d75f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 7 Dec 2021 12:27:08 +0100 Subject: [PATCH 044/145] Remove FQCN type hints on properties --- Definition.php | 2 +- DefinitionBuilder.php | 2 +- Event/Event.php | 6 +++--- Event/GuardEvent.php | 2 +- EventListener/AuditTrailListener.php | 2 +- EventListener/GuardExpression.php | 2 +- EventListener/GuardListener.php | 12 ++++++------ Exception/NotEnabledTransitionException.php | 2 +- Exception/TransitionException.php | 2 +- Workflow.php | 6 +++--- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/Definition.php b/Definition.php index 45de623..8ef23cd 100644 --- a/Definition.php +++ b/Definition.php @@ -25,7 +25,7 @@ final class Definition private array $places = []; private array $transitions = []; private array $initialPlaces = []; - private MetadataStoreInterface $metadataStore; + private $metadataStore; /** * @param string[] $places diff --git a/DefinitionBuilder.php b/DefinitionBuilder.php index 5b7a15b..908e2c4 100644 --- a/DefinitionBuilder.php +++ b/DefinitionBuilder.php @@ -25,7 +25,7 @@ class DefinitionBuilder private array $places = []; private array $transitions = []; private string|array|null $initialPlaces = null; - private ?MetadataStoreInterface $metadataStore = null; + private $metadataStore = null; /** * @param string[] $places diff --git a/Event/Event.php b/Event/Event.php index 53cc3a8..d42abdd 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -25,9 +25,9 @@ class Event extends BaseEvent { protected $context; private object $subject; - private Marking $marking; - private ?Transition $transition; - private ?WorkflowInterface $workflow; + private $marking; + private $transition; + private $workflow; public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) { diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index a3c0521..039d161 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -23,7 +23,7 @@ */ final class GuardEvent extends Event { - private TransitionBlockerList $transitionBlockerList; + private $transitionBlockerList; /** * {@inheritdoc} diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 8a7ea37..f7a8f66 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -20,7 +20,7 @@ */ class AuditTrailListener implements EventSubscriberInterface { - private LoggerInterface $logger; + private $logger; public function __construct(LoggerInterface $logger) { diff --git a/EventListener/GuardExpression.php b/EventListener/GuardExpression.php index 9fb1525..4b6d3eb 100644 --- a/EventListener/GuardExpression.php +++ b/EventListener/GuardExpression.php @@ -15,7 +15,7 @@ class GuardExpression { - private Transition $transition; + private $transition; private string $expression; public function __construct(Transition $transition, string $expression) diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 5c873d8..291602d 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -25,12 +25,12 @@ class GuardListener { private array $configuration; - private ExpressionLanguage $expressionLanguage; - private TokenStorageInterface $tokenStorage; - private AuthorizationCheckerInterface $authorizationChecker; - private AuthenticationTrustResolverInterface $trustResolver; - private ?RoleHierarchyInterface $roleHierarchy; - private ?ValidatorInterface $validator; + private $expressionLanguage; + private $tokenStorage; + private $authorizationChecker; + private $trustResolver; + private $roleHierarchy; + private $validator; public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) { diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index 4144caf..1771234 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -21,7 +21,7 @@ */ class NotEnabledTransitionException extends TransitionException { - private TransitionBlockerList $transitionBlockerList; + private $transitionBlockerList; public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, TransitionBlockerList $transitionBlockerList, array $context = []) { diff --git a/Exception/TransitionException.php b/Exception/TransitionException.php index d493e22..fad2a9d 100644 --- a/Exception/TransitionException.php +++ b/Exception/TransitionException.php @@ -21,7 +21,7 @@ class TransitionException extends LogicException { private object $subject; private string $transitionName; - private WorkflowInterface $workflow; + private $workflow; private array $context; public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, string $message, array $context = []) diff --git a/Workflow.php b/Workflow.php index 8682015..adef3ed 100644 --- a/Workflow.php +++ b/Workflow.php @@ -52,9 +52,9 @@ class Workflow implements WorkflowInterface WorkflowEvents::ANNOUNCE => self::DISABLE_ANNOUNCE_EVENT, ]; - private Definition $definition; - private MarkingStoreInterface $markingStore; - private ?EventDispatcherInterface $dispatcher; + private $definition; + private $markingStore; + private $dispatcher; private string $name; /** From f517c2aebdeeb454162bf62918f65d866d166c25 Mon Sep 17 00:00:00 2001 From: Olexandr Kalaidzhy Date: Wed, 8 Dec 2021 12:07:06 +0200 Subject: [PATCH 045/145] [Workflow] Fix eventsToDispatch parameter setup for StateMachine --- StateMachine.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/StateMachine.php b/StateMachine.php index 7bd912b..8fb4d3b 100644 --- a/StateMachine.php +++ b/StateMachine.php @@ -20,8 +20,8 @@ */ class StateMachine extends Workflow { - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') + public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) { - parent::__construct($definition, $markingStore ?? new MethodMarkingStore(true), $dispatcher, $name); + parent::__construct($definition, $markingStore ?? new MethodMarkingStore(true), $dispatcher, $name, $eventsToDispatch); } } From 43197c892cee0bdf67de86ff9d0b140692d817ab Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Tue, 14 Dec 2021 19:34:16 +0100 Subject: [PATCH 046/145] [Workflow] Add return type annotations to the Event class --- Event/Event.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Event/Event.php b/Event/Event.php index 53cc3a8..cd7fab7 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -38,16 +38,25 @@ public function __construct(object $subject, Marking $marking, Transition $trans $this->context = $context; } + /** + * @return Marking + */ public function getMarking() { return $this->marking; } + /** + * @return object + */ public function getSubject() { return $this->subject; } + /** + * @return Transition|null + */ public function getTransition() { return $this->transition; @@ -58,6 +67,9 @@ public function getWorkflow(): WorkflowInterface return $this->workflow; } + /** + * @return string + */ public function getWorkflowName() { return $this->workflow->getName(); From 99bbd1bb9a9652f4ff1f09514bc5d74fdd7f13f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 15 Dec 2021 14:29:07 +0100 Subject: [PATCH 047/145] [Workflow] Fix Event constructor requirements This has already been deprecied in 4.x But while cleaning the 5.x branch, I forgot to apply this patch See https://github.com/symfony/symfony/pull/31824/files#diff-5f386ffb0109cc731bd98e63eea021b32faadd98791bd6ba65926d09c5e2ec40L37 --- Event/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Event/Event.php b/Event/Event.php index e1f448a..19b6556 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -29,7 +29,7 @@ class Event extends BaseEvent private $transition; private $workflow; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow, array $context = []) { $this->subject = $subject; $this->marking = $marking; From d11f8a7bfcab968accb38ac17d619588adf6df4a Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Wed, 15 Dec 2021 12:30:13 +0100 Subject: [PATCH 048/145] [Workflow] Remove redundant type check --- Workflow.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Workflow.php b/Workflow.php index adef3ed..ed418aa 100644 --- a/Workflow.php +++ b/Workflow.php @@ -83,10 +83,6 @@ public function getMarking(object $subject, array $context = []): Marking { $marking = $this->markingStore->getMarking($subject); - if (!$marking instanceof Marking) { - throw new LogicException(sprintf('The value returned by the MarkingStore is not an instance of "%s" for workflow "%s".', Marking::class, $this->name)); - } - // check if the subject is already in the workflow if (!$marking->getPlaces()) { if (!$this->definition->getInitialPlaces()) { From 458677862f9434d135485db8aa8cd4a4c935c7c5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 15 Dec 2021 14:51:23 +0100 Subject: [PATCH 049/145] Revert "minor #44642 [Workflow] Fix Event constructor requirements (lyrixx)" This reverts commit 18c7edd2e481c4043a2552873ee0f62395e2b367, reversing changes made to e5d3deaf2edf469bc4c542d00d43428fa8d33dc2. --- Event/Event.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Event/Event.php b/Event/Event.php index 19b6556..e1f448a 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -29,7 +29,7 @@ class Event extends BaseEvent private $transition; private $workflow; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow, array $context = []) + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) { $this->subject = $subject; $this->marking = $marking; From 5ed28e606b0d03030a3a24a48b5b958ea188a50a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 16 Dec 2021 11:11:51 +0100 Subject: [PATCH 050/145] Add more nullsafe operators --- Dumper/GraphvizDumper.php | 2 +- Dumper/MermaidDumper.php | 2 +- Dumper/PlantUmlDumper.php | 2 +- Tests/EventListener/GuardListenerTest.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 37dad66..18d892f 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -73,7 +73,7 @@ protected function findPlaces(Definition $definition, Marking $marking = null): if (\in_array($place, $definition->getInitialPlaces(), true)) { $attributes['style'] = 'filled'; } - if ($marking && $marking->has($place)) { + if ($marking?->has($place)) { $attributes['color'] = '#FF0000'; $attributes['shape'] = 'doublecircle'; } diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 67d296e..da11298 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -73,7 +73,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $place, $meta->getPlaceMetadata($place), \in_array($place, $definition->getInitialPlaces()), - null !== $marking && $marking->has($place) + $marking?->has($place) ?? false ); $output[] = $placeNode; diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index f6b2536..9f10954 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -200,7 +200,7 @@ private function getState(string $place, Definition $definition, Marking $markin $output = "state $placeEscaped". (\in_array($place, $definition->getInitialPlaces(), true) ? ' '.self::INITIAL : ''). - ($marking && $marking->has($place) ? ' '.self::MARKED : ''); + ($marking?->has($place) ? ' '.self::MARKED : ''); $backgroundColor = $workflowMetadata->getMetadata('bg_color', $place); if (null !== $backgroundColor) { diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index a767ae5..f8907ed 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -137,7 +137,7 @@ public function testGuardExpressionBlocks() private function createEvent(Transition $transition = null) { $subject = new Subject(); - $transition = $transition ?? new Transition('name', 'from', 'to'); + $transition ??= new Transition('name', 'from', 'to'); $workflow = $this->createMock(WorkflowInterface::class); From dc0e8d7b63217563f1df9978c8522ac1fad6beb4 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 2 Jan 2022 10:41:36 +0100 Subject: [PATCH 051/145] Bump license year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index c1f0aac..a843ec1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2021 Fabien Potencier +Copyright (c) 2014-2022 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From b15420d9a72acdfc141528a2ef18d49d666fcc3f Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 9 Feb 2022 15:00:38 +0100 Subject: [PATCH 052/145] Bump minimum version of PHP to 8.1 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 63bb40e..eaa9b46 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": ">=8.0.2" + "php": ">=8.1" }, "require-dev": { "psr/log": "^1|^2|^3", From 3edf2089257c7bfa6b0f58b2f636c670b7b76909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 12 Apr 2022 17:18:48 +0200 Subject: [PATCH 053/145] Add missing license header --- Tests/DefinitionBuilderTest.php | 9 +++++ Tests/DefinitionTest.php | 9 +++++ Tests/Dumper/GraphvizDumperTest.php | 9 +++++ Tests/Dumper/PlantUmlDumperTest.php | 1 + .../Dumper/StateMachineGraphvizDumperTest.php | 9 +++++ .../EventListener/AuditTrailListenerTest.php | 9 +++++ Tests/EventListener/GuardListenerTest.php | 9 +++++ Tests/MarkingStore/MethodMarkingStoreTest.php | 9 +++++ .../MultipleStateMarkingStoreTest.php | 9 +++++ .../SingleStateMarkingStoreTest.php | 9 +++++ Tests/MarkingStore/SubjectWithType.php | 33 +++++++++++++++++++ Tests/MarkingTest.php | 9 +++++ Tests/Metadata/InMemoryMetadataStoreTest.php | 9 +++++ Tests/RegistryTest.php | 9 +++++ Tests/StateMachineTest.php | 9 +++++ Tests/Subject.php | 9 +++++ .../ClassInstanceSupportStrategyTest.php | 9 +++++ .../InstanceOfSupportStrategyTest.php | 9 +++++ Tests/TransitionTest.php | 9 +++++ Tests/Validator/StateMachineValidatorTest.php | 9 +++++ Tests/Validator/WorkflowValidatorTest.php | 9 +++++ Tests/WorkflowBuilderTrait.php | 9 +++++ Tests/WorkflowTest.php | 9 +++++ 23 files changed, 223 insertions(+) create mode 100644 Tests/MarkingStore/SubjectWithType.php diff --git a/Tests/DefinitionBuilderTest.php b/Tests/DefinitionBuilderTest.php index df8d9bb..dd65be5 100644 --- a/Tests/DefinitionBuilderTest.php +++ b/Tests/DefinitionBuilderTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index ed6e7d3..9e9c783 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index a8ca581..fcedde0 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\Dumper; use PHPUnit\Framework\TestCase; diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 5cf78fa..85c6796 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -1,4 +1,5 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\Dumper; use PHPUnit\Framework\TestCase; diff --git a/Tests/EventListener/AuditTrailListenerTest.php b/Tests/EventListener/AuditTrailListenerTest.php index 0416e7a..f499833 100644 --- a/Tests/EventListener/AuditTrailListenerTest.php +++ b/Tests/EventListener/AuditTrailListenerTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\EventListener; use PHPUnit\Framework\TestCase; diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index bc6ec0d..55c7fc2 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\EventListener; use PHPUnit\Framework\TestCase; diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 155f285..9587f92 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\MarkingStore; use PHPUnit\Framework\TestCase; diff --git a/Tests/MarkingStore/MultipleStateMarkingStoreTest.php b/Tests/MarkingStore/MultipleStateMarkingStoreTest.php index 4ca81e1..ea5a806 100644 --- a/Tests/MarkingStore/MultipleStateMarkingStoreTest.php +++ b/Tests/MarkingStore/MultipleStateMarkingStoreTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\MarkingStore; use PHPUnit\Framework\TestCase; diff --git a/Tests/MarkingStore/SingleStateMarkingStoreTest.php b/Tests/MarkingStore/SingleStateMarkingStoreTest.php index caaf977..d05df5f 100644 --- a/Tests/MarkingStore/SingleStateMarkingStoreTest.php +++ b/Tests/MarkingStore/SingleStateMarkingStoreTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\MarkingStore; use PHPUnit\Framework\TestCase; diff --git a/Tests/MarkingStore/SubjectWithType.php b/Tests/MarkingStore/SubjectWithType.php new file mode 100644 index 0000000..1040dab --- /dev/null +++ b/Tests/MarkingStore/SubjectWithType.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +class SubjectWithType +{ + private string $marking; + + public function getMarking(): string + { + return $this->marking; + } + + public function setMarking(string $type): void + { + $this->marking = $type; + } + + public function getMarking2(): string + { + // Typo made on purpose! + return $this->marking; + } +} diff --git a/Tests/MarkingTest.php b/Tests/MarkingTest.php index 9ed6df0..0a1c22b 100644 --- a/Tests/MarkingTest.php +++ b/Tests/MarkingTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index b834a60..7a1cc2b 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\Metadata; use PHPUnit\Framework\TestCase; diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index 3ad7780..dc96307 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/StateMachineTest.php b/Tests/StateMachineTest.php index a6c7362..e991707 100644 --- a/Tests/StateMachineTest.php +++ b/Tests/StateMachineTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/Subject.php b/Tests/Subject.php index 944cb22..dd63da9 100644 --- a/Tests/Subject.php +++ b/Tests/Subject.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; final class Subject diff --git a/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php b/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php index 59238e7..5593791 100644 --- a/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php +++ b/Tests/SupportStrategy/ClassInstanceSupportStrategyTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\SupportStrategy; use PHPUnit\Framework\TestCase; diff --git a/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php b/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php index a541da0..8a5c300 100644 --- a/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php +++ b/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\SupportStrategy; use PHPUnit\Framework\TestCase; diff --git a/Tests/TransitionTest.php b/Tests/TransitionTest.php index 14a646d..aee5147 100644 --- a/Tests/TransitionTest.php +++ b/Tests/TransitionTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; diff --git a/Tests/Validator/StateMachineValidatorTest.php b/Tests/Validator/StateMachineValidatorTest.php index 357e544..876e83b 100644 --- a/Tests/Validator/StateMachineValidatorTest.php +++ b/Tests/Validator/StateMachineValidatorTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\Validator; use PHPUnit\Framework\TestCase; diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index eda2d4e..5f82c3e 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests\Validator; use PHPUnit\Framework\TestCase; diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index ae48d52..bf254f0 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use Symfony\Component\Workflow\Definition; diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 7d688d0..2dca812 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow\Tests; use PHPUnit\Framework\TestCase; From d1d6ada5c204cf89e214bf1779acc619c8ba44c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 12 Apr 2022 10:02:23 +0200 Subject: [PATCH 054/145] [Workflow] Catch error when trying to get an uninitialized marking --- MarkingStore/MethodMarkingStore.php | 10 +++++- Tests/MarkingStore/MethodMarkingStoreTest.php | 30 +++++++++++++++++ Tests/MarkingStore/SubjectWithType.php | 33 +++++++++++++++++++ 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 Tests/MarkingStore/SubjectWithType.php diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index feb0129..f541d1b 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -54,7 +54,15 @@ public function getMarking($subject): Marking throw new LogicException(sprintf('The method "%s::%s()" does not exist.', \get_class($subject), $method)); } - $marking = $subject->{$method}(); + $marking = null; + try { + $marking = $subject->{$method}(); + } catch (\Error $e) { + $unInitializedPropertyMassage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); + if ($e->getMessage() !== $unInitializedPropertyMassage) { + throw $e; + } + } if (null === $marking) { return new Marking(); diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 155f285..adbdb6f 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -78,6 +78,36 @@ public function testGetMarkingWithValueObject() $this->assertSame('first_place', (string) $subject->getMarking()); } + /** + * @requires PHP 7.4 + */ + public function testGetMarkingWithUninitializedProperty() + { + $subject = new SubjectWithType(); + + $markingStore = new MethodMarkingStore(true); + + $marking = $markingStore->getMarking($subject); + + $this->assertInstanceOf(Marking::class, $marking); + $this->assertCount(0, $marking->getPlaces()); + } + + /** + * @requires PHP 7.4 + */ + public function testGetMarkingWithUninitializedProperty2() + { + $subject = new SubjectWithType(); + + $markingStore = new MethodMarkingStore(true, 'marking2'); + + $this->expectException(\Error::class); + $this->expectExceptionMessage('Typed property Symfony\Component\Workflow\Tests\MarkingStore\SubjectWithType::$marking must not be accessed before initialization'); + + $markingStore->getMarking($subject); + } + private function createValueObject(string $markingValue) { return new class($markingValue) { diff --git a/Tests/MarkingStore/SubjectWithType.php b/Tests/MarkingStore/SubjectWithType.php new file mode 100644 index 0000000..1040dab --- /dev/null +++ b/Tests/MarkingStore/SubjectWithType.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +class SubjectWithType +{ + private string $marking; + + public function getMarking(): string + { + return $this->marking; + } + + public function setMarking(string $type): void + { + $this->marking = $type; + } + + public function getMarking2(): string + { + // Typo made on purpose! + return $this->marking; + } +} From 505a77f8d85cdee13a59e793208375cf83309c5c Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Wed, 20 Apr 2022 17:01:42 +0200 Subject: [PATCH 055/145] Minor @requires function tests cleanup --- Tests/MarkingStore/MethodMarkingStoreTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 0bea476..4691e11 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -87,9 +87,6 @@ public function testGetMarkingWithValueObject() $this->assertSame('first_place', (string) $subject->getMarking()); } - /** - * @requires PHP 7.4 - */ public function testGetMarkingWithUninitializedProperty() { $subject = new SubjectWithType(); @@ -102,9 +99,6 @@ public function testGetMarkingWithUninitializedProperty() $this->assertCount(0, $marking->getPlaces()); } - /** - * @requires PHP 7.4 - */ public function testGetMarkingWithUninitializedProperty2() { $subject = new SubjectWithType(); From d5125a5e00bef03abb7c47ee4c0fca0122bf91a8 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Thu, 21 Apr 2022 12:29:03 +0200 Subject: [PATCH 056/145] [HttpClient][Translation][Workflow] [Service] Exclude tests from classmaps --- composer.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index eaa9b46..6b23eeb 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,10 @@ "symfony/event-dispatcher": "<5.4" }, "autoload": { - "psr-4": { "Symfony\\Component\\Workflow\\": "" } + "psr-4": { "Symfony\\Component\\Workflow\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "minimum-stability": "dev" } From 4334b4656bd3474ca093d2997a9feea1cc47a5e1 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 6 May 2022 10:33:13 +0200 Subject: [PATCH 057/145] [Workflow] Fix deprecated syntax for interpolated strings --- Dumper/PlantUmlDumper.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 76273c0..6e75c3c 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -103,8 +103,8 @@ public function dump(Definition $definition, Marking $marking = null, array $opt } $lines = [ - "$fromEscaped -${transitionColor}-> ${transitionEscaped}${transitionLabel}", - "$transitionEscaped -${transitionColor}-> ${toEscaped}${transitionLabel}", + "{$fromEscaped} -{$transitionColor}-> {$transitionEscaped}{$transitionLabel}", + "{$transitionEscaped} -{$transitionColor}-> {$toEscaped}{$transitionLabel}", ]; foreach ($lines as $line) { if (!\in_array($line, $code)) { @@ -112,7 +112,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt } } } else { - $code[] = "$fromEscaped -${transitionColor}-> $toEscaped: $transitionEscapedWithStyle"; + $code[] = "{$fromEscaped} -{$transitionColor}-> {$toEscaped}: {$transitionEscapedWithStyle}"; } } } From 8910b89520aca671feb3c625dd30b8c2ca5ed7fa Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 31 May 2022 18:48:23 +0200 Subject: [PATCH 058/145] [Workflow] Add ZEturf as backer --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 66fb601..6cc5440 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,19 @@ Workflow Component The Workflow component provides tools for managing a workflow or finite state machine. +Sponsor +------- + +The Workflow component for Symfony 5.4/6.0 is [backed][1] by [ZEturf][2]. + +The ZEturf group is an online gaming operator licensed in 6 markets in Europe and +Africa. It operates two brands, ZEturf on horse racing betting, and ZEbet on +sports betting. In parallel with the development of its betting activities, the +group is also investing in Entertainment / gaming with Free-to-Play and +Play-to-Earn projects. + +Help Symfony by [sponsoring][3] its development! + Resources --------- @@ -12,3 +25,7 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://zeturf.com +[3]: https://symfony.com/sponsor From fbdce270201fc9753414298d40be1575891b1682 Mon Sep 17 00:00:00 2001 From: Nyholm Date: Tue, 14 Jun 2022 19:04:55 +0200 Subject: [PATCH 059/145] [Workflow] Add a nice exception message when unexpected data is passed to Marking --- MarkingStore/MethodMarkingStore.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index b676bd5..482d7ec 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -70,6 +70,8 @@ public function getMarking(object $subject): Marking if ($this->singleState) { $marking = [(string) $marking => 1]; + } elseif (!is_array($marking)) { + throw new LogicException(sprintf('The method "%s::%s()" did not return an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $method)); } return new Marking($marking); From f1dce66720da22d04e2d8d4073de3abd0d1c057b Mon Sep 17 00:00:00 2001 From: Bart Brouwer Date: Thu, 7 Jul 2022 14:47:12 +0200 Subject: [PATCH 060/145] [Workflow] Fix typo in MethodMarkingStore --- MarkingStore/MethodMarkingStore.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index f541d1b..22e6c8c 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -58,8 +58,8 @@ public function getMarking($subject): Marking try { $marking = $subject->{$method}(); } catch (\Error $e) { - $unInitializedPropertyMassage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); - if ($e->getMessage() !== $unInitializedPropertyMassage) { + $unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); + if ($e->getMessage() !== $unInitializedPropertyMessage) { throw $e; } } From 5049eb0210ab326b7a9c4a43c3ae0f52b1dd5f1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 12 Aug 2022 11:52:54 +0200 Subject: [PATCH 061/145] [Workflow] Mark registry as internal and deprecate the service Instead, all workflow services are tagged and can be injected with the following YAML syntax: ```yaml !tagged_locator { tag: workflow, index_by: name } ``` or PHP syntax: ```php tagged_locator('workflow', 'name') ``` Also, two others tags exists for each workflow types * `workflow.workflow` * `workflow.state_machine` --- CHANGELOG.md | 5 +++++ Registry.php | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b533107..758606b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +6.2 +--- + + * Mark `Symfony\Component\Workflow\Registry` as internal + 6.0 --- diff --git a/Registry.php b/Registry.php index 1fbc76b..a9c21af 100644 --- a/Registry.php +++ b/Registry.php @@ -17,6 +17,8 @@ /** * @author Fabien Potencier * @author Grégoire Pineau + * + * @internal since Symfony 6.2. Inject the workflow where you need it. */ class Registry { From 93f2d3bd17d86536fdb1d9e49dcfdf04c238f076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 25 Aug 2022 16:59:21 +0200 Subject: [PATCH 062/145] [CS] Remove @inheritdoc PHPDoc --- Dumper/GraphvizDumper.php | 2 -- Dumper/StateMachineGraphvizDumper.php | 2 -- Event/GuardEvent.php | 3 --- MarkingStore/MethodMarkingStore.php | 6 ----- SupportStrategy/InstanceOfSupportStrategy.php | 3 --- Workflow.php | 27 ------------------- 6 files changed, 43 deletions(-) diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 18d892f..01a5b36 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -34,8 +34,6 @@ class GraphvizDumper implements DumperInterface ]; /** - * {@inheritdoc} - * * Dumps the workflow as a graphviz graph. * * Available options: diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index 95b19c4..ac90b61 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -17,8 +17,6 @@ class StateMachineGraphvizDumper extends GraphvizDumper { /** - * {@inheritdoc} - * * Dumps the workflow as a graphviz graph. * * Available options: diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index a3c0521..9409da2 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -25,9 +25,6 @@ final class GuardEvent extends Event { private TransitionBlockerList $transitionBlockerList; - /** - * {@inheritdoc} - */ public function __construct(object $subject, Marking $marking, Transition $transition, WorkflowInterface $workflow = null) { parent::__construct($subject, $marking, $transition, $workflow); diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index 9790bce..b1602dc 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -43,9 +43,6 @@ public function __construct(bool $singleState = false, string $property = 'marki $this->property = $property; } - /** - * {@inheritdoc} - */ public function getMarking(object $subject): Marking { $method = 'get'.ucfirst($this->property); @@ -77,9 +74,6 @@ public function getMarking(object $subject): Marking return new Marking($marking); } - /** - * {@inheritdoc} - */ public function setMarking(object $subject, Marking $marking, array $context = []) { $marking = $marking->getPlaces(); diff --git a/SupportStrategy/InstanceOfSupportStrategy.php b/SupportStrategy/InstanceOfSupportStrategy.php index a00aba5..86bd107 100644 --- a/SupportStrategy/InstanceOfSupportStrategy.php +++ b/SupportStrategy/InstanceOfSupportStrategy.php @@ -26,9 +26,6 @@ public function __construct(string $className) $this->className = $className; } - /** - * {@inheritdoc} - */ public function supports(WorkflowInterface $workflow, object $subject): bool { return $subject instanceof $this->className; diff --git a/Workflow.php b/Workflow.php index 2901878..3d4e856 100644 --- a/Workflow.php +++ b/Workflow.php @@ -76,9 +76,6 @@ public function __construct(Definition $definition, MarkingStoreInterface $marki $this->eventsToDispatch = $eventsToDispatch; } - /** - * {@inheritdoc} - */ public function getMarking(object $subject, array $context = []): Marking { $marking = $this->markingStore->getMarking($subject); @@ -118,9 +115,6 @@ public function getMarking(object $subject, array $context = []): Marking return $marking; } - /** - * {@inheritdoc} - */ public function can(object $subject, string $transitionName): bool { $transitions = $this->definition->getTransitions(); @@ -141,9 +135,6 @@ public function can(object $subject, string $transitionName): bool return false; } - /** - * {@inheritdoc} - */ public function buildTransitionBlockerList(object $subject, string $transitionName): TransitionBlockerList { $transitions = $this->definition->getTransitions(); @@ -177,9 +168,6 @@ public function buildTransitionBlockerList(object $subject, string $transitionNa return $transitionBlockerList; } - /** - * {@inheritdoc} - */ public function apply(object $subject, string $transitionName, array $context = []): Marking { $marking = $this->getMarking($subject, $context); @@ -245,9 +233,6 @@ public function apply(object $subject, string $transitionName, array $context = return $marking; } - /** - * {@inheritdoc} - */ public function getEnabledTransitions(object $subject): array { $enabledTransitions = []; @@ -282,33 +267,21 @@ public function getEnabledTransition(object $subject, string $name): ?Transition return null; } - /** - * {@inheritdoc} - */ public function getName(): string { return $this->name; } - /** - * {@inheritdoc} - */ public function getDefinition(): Definition { return $this->definition; } - /** - * {@inheritdoc} - */ public function getMarkingStore(): MarkingStoreInterface { return $this->markingStore; } - /** - * {@inheritdoc} - */ public function getMetadataStore(): MetadataStoreInterface { return $this->definition->getMetadataStore(); From 3ee1548e20d41db358b72f370b4cc632a940298c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 12 Sep 2022 14:32:45 +0200 Subject: [PATCH 063/145] Remove all "nullable-by-default-value" setters --- CHANGELOG.md | 1 + Definition.php | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 758606b..808ad77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Mark `Symfony\Component\Workflow\Registry` as internal + * Deprecate calling `Definition::setInitialPlaces()` without arguments 6.0 --- diff --git a/Definition.php b/Definition.php index 45de623..c3a0637 100644 --- a/Definition.php +++ b/Definition.php @@ -78,6 +78,9 @@ public function getMetadataStore(): MetadataStoreInterface private function setInitialPlaces(string|array $places = null) { + if (1 > \func_num_args()) { + trigger_deprecation('symfony/workflow', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); + } if (!$places) { return; } From 63f9f9ade8abf31787769bcdfb5ad04f6e44ab0f Mon Sep 17 00:00:00 2001 From: tigitz Date: Fri, 30 Sep 2022 23:29:24 +0200 Subject: [PATCH 064/145] Leverage class name literal on object --- EventListener/AuditTrailListener.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 8a7ea37..de89269 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -30,19 +30,19 @@ public function __construct(LoggerInterface $logger) public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } public function onTransition(Event $event) { - $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, \get_class($event->getSubject()), $event->getWorkflowName())); + $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } From 245065b70912c1a4ffe78db8987b33d73586331a Mon Sep 17 00:00:00 2001 From: Kamil Musial Date: Wed, 21 Dec 2022 12:47:21 +0100 Subject: [PATCH 065/145] [Workflow] Allow spaces in place names so the PUML dump doesn't break --- Dumper/PlantUmlDumper.php | 7 ++--- Tests/Dumper/PlantUmlDumperTest.php | 31 +++++++++++++++++++ .../puml/square/simple-workflow-marking.puml | 4 +-- .../square/simple-workflow-nomarking.puml | 4 +-- .../square/simple-workflow-with-spaces.puml | 23 ++++++++++++++ 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 Tests/fixtures/puml/square/simple-workflow-with-spaces.puml diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 6e75c3c..43595c6 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Workflow\Dumper; -use InvalidArgumentException; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; @@ -57,7 +56,7 @@ class PlantUmlDumper implements DumperInterface public function __construct(string $transitionType = null) { if (!\in_array($transitionType, self::TRANSITION_TYPES, true)) { - throw new InvalidArgumentException("Transition type '$transitionType' does not exist."); + throw new \InvalidArgumentException("Transition type '$transitionType' does not exist."); } $this->transitionType = $transitionType; } @@ -209,9 +208,7 @@ private function getState(string $place, Definition $definition, Marking $markin $description = $workflowMetadata->getMetadata('description', $place); if (null !== $description) { - $output .= ' as '.$place. - \PHP_EOL. - $place.' : '.$description; + $output .= \PHP_EOL.$placeEscaped.' : '.$description; } return $output; diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 85c6796..0c750fc 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -12,9 +12,12 @@ namespace Symfony\Component\Workflow\Tests\Dumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Dumper\PlantUmlDumper; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore; use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; +use Symfony\Component\Workflow\Transition; class PlantUmlDumperTest extends TestCase { @@ -63,6 +66,34 @@ public function provideStateMachineDefinitionWithoutMarking() yield [$this->createComplexStateMachineDefinition(), $marking, 'complex-state-machine-marking', 'SimpleDiagram']; } + public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() + { + $dumper = new PlantUmlDumper(PlantUmlDumper::WORKFLOW_TRANSITION); + + // The graph looks like: + // + // +---------+ t 1 +----------+ | + // | place a | -----> | place b | | + // +---------+ +----------+ | + $places = ['place a', 'place b']; + + $transitions = []; + $transition = new Transition('t 1', 'place a', 'place b'); + $transitions[] = $transition; + + $placesMetadata = []; + $placesMetadata['place a'] = [ + 'description' => 'My custom place description', + ]; + $inMemoryMetadataStore = new InMemoryMetadataStore([], $placesMetadata); + $definition = new Definition($places, $transitions, null, $inMemoryMetadataStore); + + $dump = $dumper->dump($definition, null, ['title' => 'SimpleDiagram']); + $dump = str_replace(\PHP_EOL, "\n", $dump.\PHP_EOL); + $file = $this->getFixturePath('simple-workflow-with-spaces', PlantUmlDumper::WORKFLOW_TRANSITION); + $this->assertStringEqualsFile($file, $dump); + } + private function getFixturePath($name, $transitionType) { return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml'; diff --git a/Tests/fixtures/puml/square/simple-workflow-marking.puml b/Tests/fixtures/puml/square/simple-workflow-marking.puml index 0ea138f..1e8a2ea 100644 --- a/Tests/fixtures/puml/square/simple-workflow-marking.puml +++ b/Tests/fixtures/puml/square/simple-workflow-marking.puml @@ -17,8 +17,8 @@ skinparam agent { } state "a" <> state "b" <> -state "c" <> as c -c : My custom place description +state "c" <> +"c" : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/Tests/fixtures/puml/square/simple-workflow-nomarking.puml b/Tests/fixtures/puml/square/simple-workflow-nomarking.puml index 02e7f39..b57dc5b 100644 --- a/Tests/fixtures/puml/square/simple-workflow-nomarking.puml +++ b/Tests/fixtures/puml/square/simple-workflow-nomarking.puml @@ -17,8 +17,8 @@ skinparam agent { } state "a" <> state "b" -state "c" <> as c -c : My custom place description +state "c" <> +"c" : My custom place description agent "t1" agent "t2" "a" -[#Purple]-> "t1": "My custom transition label 2" diff --git a/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml b/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml new file mode 100644 index 0000000..0e20d27 --- /dev/null +++ b/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml @@ -0,0 +1,23 @@ +@startuml +allow_mixing +title SimpleDiagram +skinparam titleBorderRoundCorner 15 +skinparam titleBorderThickness 2 +skinparam state { + BackgroundColor<> #87b741 + BackgroundColor<> #3887C6 + BorderColor #3887C6 + BorderColor<> Black + FontColor<> White +} +skinparam agent { + BackgroundColor #ffffff + BorderColor #3887C6 +} +state "place a" <> +"place a" : My custom place description +state "place b" +agent "t 1" +"place a" --> "t 1" +"t 1" --> "place b" +@enduml From f9ea842983035ad836b78a9f9363f2bdaa2a8bb1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 1 Jan 2023 09:32:19 +0100 Subject: [PATCH 066/145] Bump license year to 2023 --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index a843ec1..72412a6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2022 Fabien Potencier +Copyright (c) 2014-2023 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 55317aa66d2dc38b89a260fc461aec797c169e6f Mon Sep 17 00:00:00 2001 From: tigitz Date: Sun, 1 Jan 2023 19:45:34 +0100 Subject: [PATCH 067/145] Leverage arrow function syntax for closure --- EventListener/ExpressionLanguage.php | 10 ++-------- Registry.php | 4 +--- Tests/RegistryTest.php | 4 +--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index 6fb4307..91957a4 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -26,15 +26,9 @@ protected function registerFunctions() { parent::registerFunctions(); - $this->register('is_granted', function ($attributes, $object = 'null') { - return sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object); - }, function (array $variables, $attributes, $object = null) { - return $variables['auth_checker']->isGranted($attributes, $object); - }); + $this->register('is_granted', fn ($attributes, $object = 'null') => sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); - $this->register('is_valid', function ($object = 'null', $groups = 'null') { - return sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups); - }, function (array $variables, $object = null, $groups = null) { + $this->register('is_valid', fn ($object = 'null', $groups = 'null') => sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed.'); } diff --git a/Registry.php b/Registry.php index a9c21af..9f3171f 100644 --- a/Registry.php +++ b/Registry.php @@ -55,9 +55,7 @@ public function get(object $subject, string $workflowName = null): Workflow } if (2 <= \count($matched)) { - $names = array_map(static function (WorkflowInterface $workflow): string { - return $workflow->getName(); - }, $matched); + $names = array_map(static fn (WorkflowInterface $workflow): string => $workflow->getName(), $matched); throw new InvalidArgumentException(sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); } diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index a0d9c0d..cf3ee8c 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -112,9 +112,7 @@ private function createWorkflowSupportStrategy($supportedClassName) { $strategy = $this->createMock(WorkflowSupportStrategyInterface::class); $strategy->expects($this->any())->method('supports') - ->willReturnCallback(function ($workflow, $subject) use ($supportedClassName) { - return $subject instanceof $supportedClassName; - }); + ->willReturnCallback(fn ($workflow, $subject) => $subject instanceof $supportedClassName); return $strategy; } From cea2c95c2dd7d9a7675a9b7374e4ae274d4d3d39 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 20 Jan 2023 19:30:11 +0100 Subject: [PATCH 068/145] [Workflow] Add bitExpert as a backer to the README --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 66fb601..822a29d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,22 @@ Workflow Component The Workflow component provides tools for managing a workflow or finite state machine. +Sponsor +------- + +The Workflow component for Symfony 6.2 is [backed][1] by [bitExpert][2]. + +Their pulse is cross-technology software development that beats with every line of code. +The basic principle for their solutions, products, and services is innovation, quality, +commitment, and professionalism. + +bitExpert actively supports Open-Source and the software development community through various +activities: Contributing to the Open-Source projects they love, organizing & hosting meetups, +speaking at conferences, and organizing unKonf - an unconference focused on web +and software development practices. + +Help Symfony by [sponsoring][3] its development! + Resources --------- @@ -12,3 +28,7 @@ Resources * [Report issues](https://github.com/symfony/symfony/issues) and [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) + +[1]: https://symfony.com/backers +[2]: https://www.bitexpert.de +[3]: https://symfony.com/sponsor From 7dcb9e82ef32773e1361b35ff35443210a67fbd8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 24 Jan 2023 15:02:24 +0100 Subject: [PATCH 069/145] Update license years (last time) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 72412a6..29f72d5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2014-2023 Fabien Potencier +Copyright (c) 2014-present Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From 5adcd79aa0fdf7cb8f99f82bb77b5c316cf9ccf5 Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Tue, 31 Jan 2023 23:27:15 +0100 Subject: [PATCH 070/145] remove unused parameter transitionId in MermaidDumper --- Dumper/MermaidDumper.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index da11298..27b8dd0 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -105,7 +105,6 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $transitionOutput = $this->styleStatemachineTransition( $from, $to, - $transitionId, $transitionLabel, $transitionMeta ); @@ -211,7 +210,6 @@ private function validateTransitionType(string $transitionType): void private function styleStatemachineTransition( string $from, string $to, - int $transitionId, string $transitionLabel, array $transitionMeta ): array { From 8854493d4f5d4409833cb116360aadd2420e91e2 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 7 Feb 2023 21:09:05 +0100 Subject: [PATCH 071/145] [Tests] Migrate tests to static data providers --- Tests/Dumper/GraphvizDumperTest.php | 24 ++++++++++++------------ Tests/Dumper/MermaidDumperTest.php | 18 +++++++++--------- Tests/Dumper/PlantUmlDumperTest.php | 16 ++++++++-------- Tests/WorkflowBuilderTrait.php | 8 ++++---- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index fcedde0..f18868f 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -47,28 +47,28 @@ public function testDumpWithMarking($definition, $marking, $expected) $this->assertEquals($expected, $dump); } - public function provideWorkflowDefinitionWithMarking() + public static function provideWorkflowDefinitionWithMarking() { yield [ - $this->createComplexWorkflowDefinition(), + self::createComplexWorkflowDefinition(), new Marking(['b' => 1]), - $this->createComplexWorkflowDefinitionDumpWithMarking(), + self::createComplexWorkflowDefinitionDumpWithMarking(), ]; yield [ - $this->createSimpleWorkflowDefinition(), + self::createSimpleWorkflowDefinition(), new Marking(['c' => 1, 'd' => 1]), - $this->createSimpleWorkflowDumpWithMarking(), + self::createSimpleWorkflowDumpWithMarking(), ]; } - public function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking() { - yield [$this->createComplexWorkflowDefinition(), $this->provideComplexWorkflowDumpWithoutMarking()]; - yield [$this->createSimpleWorkflowDefinition(), $this->provideSimpleWorkflowDumpWithoutMarking()]; + yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarking()]; + yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarking()]; } - public function createComplexWorkflowDefinitionDumpWithMarking() + public static function createComplexWorkflowDefinitionDumpWithMarking() { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -106,7 +106,7 @@ public function createComplexWorkflowDefinitionDumpWithMarking() '; } - public function createSimpleWorkflowDumpWithMarking() + public static function createSimpleWorkflowDumpWithMarking() { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -126,7 +126,7 @@ public function createSimpleWorkflowDumpWithMarking() '; } - public function provideComplexWorkflowDumpWithoutMarking() + public static function provideComplexWorkflowDumpWithoutMarking() { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -164,7 +164,7 @@ public function provideComplexWorkflowDumpWithoutMarking() '; } - public function provideSimpleWorkflowDumpWithoutMarking() + public static function provideSimpleWorkflowDumpWithoutMarking() { return 'digraph workflow { ratio="compress" rankdir="LR" diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php index 4eaebe1..93c1e33 100644 --- a/Tests/Dumper/MermaidDumperTest.php +++ b/Tests/Dumper/MermaidDumperTest.php @@ -71,11 +71,11 @@ public function testDumpWorkflowWithMarking(Definition $definition, Marking $mar $this->assertEquals($expected, $dump); } - public function provideWorkflowDefinitionWithoutMarking(): array + public static function provideWorkflowDefinitionWithoutMarking(): array { return [ [ - $this->createComplexWorkflowDefinition(), + self::createComplexWorkflowDefinition(), "graph LR\n" ."a0([\"a\"])\n" ."b1((\"b\"))\n" @@ -108,7 +108,7 @@ public function provideWorkflowDefinitionWithoutMarking(): array .'transition5-->g6', ], [ - $this->createWorkflowWithSameNameTransition(), + self::createWorkflowWithSameNameTransition(), "graph LR\n" ."a0([\"a\"])\n" ."b1((\"b\"))\n" @@ -128,7 +128,7 @@ public function provideWorkflowDefinitionWithoutMarking(): array .'transition3-->a0', ], [ - $this->createSimpleWorkflowDefinition(), + self::createSimpleWorkflowDefinition(), "graph LR\n" ."a0([\"a\"])\n" ."b1((\"b\"))\n" @@ -146,7 +146,7 @@ public function provideWorkflowDefinitionWithoutMarking(): array ]; } - public function provideWorkflowWithReservedWords() + public static function provideWorkflowWithReservedWords() { $builder = new DefinitionBuilder(); @@ -177,11 +177,11 @@ public function provideWorkflowWithReservedWords() ]; } - public function provideStatemachine(): array + public static function provideStatemachine(): array { return [ [ - $this->createComplexStateMachineDefinition(), + self::createComplexStateMachineDefinition(), "graph LR\n" ."a0([\"a\"])\n" ."b1((\"b\"))\n" @@ -196,7 +196,7 @@ public function provideStatemachine(): array ]; } - public function provideWorkflowWithMarking(): array + public static function provideWorkflowWithMarking(): array { $marking = new Marking(); $marking->mark('b'); @@ -204,7 +204,7 @@ public function provideWorkflowWithMarking(): array return [ [ - $this->createSimpleWorkflowDefinition(), + self::createSimpleWorkflowDefinition(), $marking, "graph LR\n" ."a0([\"a\"])\n" diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 0c750fc..6af7809 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -36,14 +36,14 @@ public function testDumpWorkflowWithoutMarking($definition, $marking, $expectedF $this->assertStringEqualsFile($file, $dump); } - public function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking() { - yield [$this->createSimpleWorkflowDefinition(), null, 'simple-workflow-nomarking', 'SimpleDiagram']; - yield [$this->createComplexWorkflowDefinition(), null, 'complex-workflow-nomarking', 'ComplexDiagram']; + yield [self::createSimpleWorkflowDefinition(), null, 'simple-workflow-nomarking', 'SimpleDiagram']; + yield [self::createComplexWorkflowDefinition(), null, 'complex-workflow-nomarking', 'ComplexDiagram']; $marking = new Marking(['b' => 1]); - yield [$this->createSimpleWorkflowDefinition(), $marking, 'simple-workflow-marking', 'SimpleDiagram']; + yield [self::createSimpleWorkflowDefinition(), $marking, 'simple-workflow-marking', 'SimpleDiagram']; $marking = new Marking(['c' => 1, 'e' => 1]); - yield [$this->createComplexWorkflowDefinition(), $marking, 'complex-workflow-marking', 'ComplexDiagram']; + yield [self::createComplexWorkflowDefinition(), $marking, 'complex-workflow-marking', 'ComplexDiagram']; } /** @@ -59,11 +59,11 @@ public function testDumpStateMachineWithoutMarking($definition, $marking, $expec $this->assertStringEqualsFile($file, $dump); } - public function provideStateMachineDefinitionWithoutMarking() + public static function provideStateMachineDefinitionWithoutMarking() { - yield [$this->createComplexStateMachineDefinition(), null, 'complex-state-machine-nomarking', 'SimpleDiagram']; + yield [static::createComplexStateMachineDefinition(), null, 'complex-state-machine-nomarking', 'SimpleDiagram']; $marking = new Marking(['c' => 1, 'e' => 1]); - yield [$this->createComplexStateMachineDefinition(), $marking, 'complex-state-machine-marking', 'SimpleDiagram']; + yield [static::createComplexStateMachineDefinition(), $marking, 'complex-state-machine-marking', 'SimpleDiagram']; } public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index bf254f0..0c6db39 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -17,7 +17,7 @@ trait WorkflowBuilderTrait { - private function createComplexWorkflowDefinition() + private static function createComplexWorkflowDefinition() { $places = range('a', 'g'); @@ -52,7 +52,7 @@ private function createComplexWorkflowDefinition() // +----+ +----+ +----+ +----+ } - private function createSimpleWorkflowDefinition() + private static function createSimpleWorkflowDefinition() { $places = range('a', 'c'); @@ -87,7 +87,7 @@ private function createSimpleWorkflowDefinition() // +---+ +----+ +---+ +----+ +---+ } - private function createWorkflowWithSameNameTransition() + private static function createWorkflowWithSameNameTransition() { $places = range('a', 'c'); @@ -115,7 +115,7 @@ private function createWorkflowWithSameNameTransition() // +--------------------------------------------------------------------+ } - private function createComplexStateMachineDefinition() + private static function createComplexStateMachineDefinition() { $places = ['a', 'b', 'c', 'd']; From 403b0b9f359ba35765f448e94addd41c52607fb4 Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 13 Feb 2023 00:00:11 +0100 Subject: [PATCH 072/145] Add missing PHPdoc return types --- EventListener/GuardExpression.php | 6 ++++++ Exception/TransitionException.php | 3 +++ Marking.php | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/EventListener/GuardExpression.php b/EventListener/GuardExpression.php index 9fb1525..23e830c 100644 --- a/EventListener/GuardExpression.php +++ b/EventListener/GuardExpression.php @@ -24,11 +24,17 @@ public function __construct(Transition $transition, string $expression) $this->expression = $expression; } + /** + * @return Transition + */ public function getTransition() { return $this->transition; } + /** + * @return string + */ public function getExpression() { return $this->expression; diff --git a/Exception/TransitionException.php b/Exception/TransitionException.php index d493e22..890d8e2 100644 --- a/Exception/TransitionException.php +++ b/Exception/TransitionException.php @@ -34,6 +34,9 @@ public function __construct(object $subject, string $transitionName, WorkflowInt $this->context = $context; } + /** + * @return object + */ public function getSubject() { return $this->subject; diff --git a/Marking.php b/Marking.php index 36deb2d..dba7c3d 100644 --- a/Marking.php +++ b/Marking.php @@ -41,11 +41,17 @@ public function unmark(string $place) unset($this->places[$place]); } + /** + * @return bool + */ public function has(string $place) { return isset($this->places[$place]); } + /** + * @return array + */ public function getPlaces() { return $this->places; From 99889c29e857d7db67b021ce987cbcd278758cac Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Mon, 13 Feb 2023 00:00:27 +0100 Subject: [PATCH 073/145] Add PHP types to private methods and functions --- Dumper/MermaidDumper.php | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 27b8dd0..20897f7 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -186,7 +186,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal * Replace double quotes with the mermaid escape syntax and * ensure all other characters are properly escaped. */ - private function escape(string $label) + private function escape(string $label): string { $label = str_replace('"', '#quot;', $label); @@ -207,12 +207,8 @@ private function validateTransitionType(string $transitionType): void } } - private function styleStatemachineTransition( - string $from, - string $to, - string $transitionLabel, - array $transitionMeta - ): array { + private function styleStatemachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array + { $transitionOutput = [sprintf('%s-->|%s|%s', $from, $this->escape($transitionLabel), $to)]; $linkStyle = $this->styleLink($transitionMeta); @@ -225,13 +221,8 @@ private function styleStatemachineTransition( return $transitionOutput; } - private function styleWorkflowTransition( - string $from, - string $to, - int $transitionId, - string $transitionLabel, - array $transitionMeta - ) { + private function styleWorkflowTransition(string $from, string $to, int $transitionId, string $transitionLabel, array $transitionMeta): array + { $transitionOutput = []; $transitionLabel = $this->escape($transitionLabel); From 105350a417dd2c5e1b4f9ddb70b1fa0b0b8f838b Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 12 Feb 2023 23:57:18 +0100 Subject: [PATCH 074/145] Add void return types --- Definition.php | 6 +++--- EventListener/AuditTrailListener.php | 9 +++++++++ EventListener/ExpressionLanguage.php | 3 +++ EventListener/GuardListener.php | 5 ++++- Marking.php | 6 ++++++ MarkingStore/MethodMarkingStore.php | 2 +- Registry.php | 3 +++ Validator/StateMachineValidator.php | 3 +++ Validator/WorkflowValidator.php | 3 +++ 9 files changed, 35 insertions(+), 5 deletions(-) diff --git a/Definition.php b/Definition.php index c3a0637..cdb1809 100644 --- a/Definition.php +++ b/Definition.php @@ -76,7 +76,7 @@ public function getMetadataStore(): MetadataStoreInterface return $this->metadataStore; } - private function setInitialPlaces(string|array $places = null) + private function setInitialPlaces(string|array $places = null): void { if (1 > \func_num_args()) { trigger_deprecation('symfony/workflow', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); @@ -96,7 +96,7 @@ private function setInitialPlaces(string|array $places = null) $this->initialPlaces = $places; } - private function addPlace(string $place) + private function addPlace(string $place): void { if (!\count($this->places)) { $this->initialPlaces = [$place]; @@ -105,7 +105,7 @@ private function addPlace(string $place) $this->places[$place] = $place; } - private function addTransition(Transition $transition) + private function addTransition(Transition $transition): void { $name = $transition->getName(); diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index de89269..8d82824 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -27,6 +27,9 @@ public function __construct(LoggerInterface $logger) $this->logger = $logger; } + /** + * @return void + */ public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { @@ -34,11 +37,17 @@ public function onLeave(Event $event) } } + /** + * @return void + */ public function onTransition(Event $event) { $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } + /** + * @return void + */ public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index 91957a4..ab5ea51 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -22,6 +22,9 @@ */ class ExpressionLanguage extends BaseExpressionLanguage { + /** + * @return void + */ protected function registerFunctions() { parent::registerFunctions(); diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 5c873d8..c207b1a 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -43,6 +43,9 @@ public function __construct(array $configuration, ExpressionLanguage $expression $this->validator = $validator; } + /** + * @return void + */ public function onTransition(GuardEvent $event, string $eventName) { if (!isset($this->configuration[$eventName])) { @@ -62,7 +65,7 @@ public function onTransition(GuardEvent $event, string $eventName) } } - private function validateGuardExpression(GuardEvent $event, string $expression) + private function validateGuardExpression(GuardEvent $event, string $expression): void { if (!$this->expressionLanguage->evaluate($expression, $this->getVariables($event))) { $blocker = TransitionBlocker::createBlockedByExpressionGuardListener($expression); diff --git a/Marking.php b/Marking.php index dba7c3d..95a83f0 100644 --- a/Marking.php +++ b/Marking.php @@ -31,11 +31,17 @@ public function __construct(array $representation = []) } } + /** + * @return void + */ public function mark(string $place) { $this->places[$place] = 1; } + /** + * @return void + */ public function unmark(string $place) { unset($this->places[$place]); diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index b1602dc..78d3307 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -74,7 +74,7 @@ public function getMarking(object $subject): Marking return new Marking($marking); } - public function setMarking(object $subject, Marking $marking, array $context = []) + public function setMarking(object $subject, Marking $marking, array $context = []): void { $marking = $marking->getPlaces(); diff --git a/Registry.php b/Registry.php index 9f3171f..287d8b7 100644 --- a/Registry.php +++ b/Registry.php @@ -24,6 +24,9 @@ class Registry { private array $workflows = []; + /** + * @return void + */ public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategyInterface $supportStrategy) { $this->workflows[] = [$workflow, $supportStrategy]; diff --git a/Validator/StateMachineValidator.php b/Validator/StateMachineValidator.php index d4e78ca..20afc8d 100644 --- a/Validator/StateMachineValidator.php +++ b/Validator/StateMachineValidator.php @@ -19,6 +19,9 @@ */ class StateMachineValidator implements DefinitionValidatorInterface { + /** + * @return void + */ public function validate(Definition $definition, string $name) { $transitionFromNames = []; diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index 6a5c76d..c13c281 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -27,6 +27,9 @@ public function __construct(bool $singlePlace = false) $this->singlePlace = $singlePlace; } + /** + * @return void + */ public function validate(Definition $definition, string $name) { // Make sure all transitions for one place has unique name. From 432fdba37182ffcf3c0eca2efc673bca38bc7599 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 14 Dec 2022 15:42:16 +0100 Subject: [PATCH 075/145] Migrate to `static` data providers using `rector/rector` --- Tests/WorkflowTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 6399bf2..6d84b19 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -438,7 +438,7 @@ public function testApplyWithEventDispatcher() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public function provideApplyWithEventDispatcherForAnnounceTests() + public static function provideApplyWithEventDispatcherForAnnounceTests() { yield [false, [Workflow::DISABLE_ANNOUNCE_EVENT => true]]; yield [true, [Workflow::DISABLE_ANNOUNCE_EVENT => false]]; From a025513534b97a9db0111ec2ecc9ade9c8bc53ff Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Tue, 7 Feb 2023 00:45:57 +0100 Subject: [PATCH 076/145] [Workflow] remove new lines from workflow metadata --- Dumper/MermaidDumper.php | 2 +- Dumper/PlantUmlDumper.php | 5 +++-- Tests/Dumper/StateMachineGraphvizDumperTest.php | 6 ++++-- Tests/WorkflowBuilderTrait.php | 7 ++++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 15ec8c6..b7f5eae 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -224,7 +224,7 @@ private function styleStatemachineTransition( string $transitionLabel, array $transitionMeta ): array { - $transitionOutput = [sprintf('%s-->|%s|%s', $from, $this->escape($transitionLabel), $to)]; + $transitionOutput = [sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 43595c6..f9a9957 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -195,7 +195,7 @@ private function getState(string $place, Definition $definition, Marking $markin { $workflowMetadata = $definition->getMetadataStore(); - $placeEscaped = $this->escape($place); + $placeEscaped = str_replace("\n", ' ', $this->escape($place)); $output = "state $placeEscaped". (\in_array($place, $definition->getInitialPlaces(), true) ? ' '.self::INITIAL : ''). @@ -208,7 +208,7 @@ private function getState(string $place, Definition $definition, Marking $markin $description = $workflowMetadata->getMetadata('description', $place); if (null !== $description) { - $output .= \PHP_EOL.$placeEscaped.' : '.$description; + $output .= \PHP_EOL.$placeEscaped.' : '.str_replace("\n", ' ', $description); } return $output; @@ -217,6 +217,7 @@ private function getState(string $place, Definition $definition, Marking $markin private function getTransitionEscapedWithStyle(MetadataStoreInterface $workflowMetadata, Transition $transition, string $to): string { $to = $workflowMetadata->getMetadata('label', $transition) ?? $to; + $to = str_replace("\n", ' ', $to); $color = $workflowMetadata->getMetadata('color', $transition) ?? null; diff --git a/Tests/Dumper/StateMachineGraphvizDumperTest.php b/Tests/Dumper/StateMachineGraphvizDumperTest.php index a7253ec..a45e07c 100644 --- a/Tests/Dumper/StateMachineGraphvizDumperTest.php +++ b/Tests/Dumper/StateMachineGraphvizDumperTest.php @@ -44,7 +44,8 @@ public function testDumpWithoutMarking() place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label="c", shape=circle]; place_3c363836cf4e16666669a25da280a1865c2d2874 [label="d", shape=circle]; place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="t1" style="solid"]; - place_3c363836cf4e16666669a25da280a1865c2d2874 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="My custom transition label 3" style="solid" fontcolor="Grey" color="Red"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="My custom transition +label 3" style="solid" fontcolor="Grey" color="Red"]; place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label="t2" style="solid" color="Blue"]; place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> place_3c363836cf4e16666669a25da280a1865c2d2874 [label="t3" style="solid"]; } @@ -70,7 +71,8 @@ public function testDumpWithMarking() place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label="c", shape=circle]; place_3c363836cf4e16666669a25da280a1865c2d2874 [label="d", shape=circle]; place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="t1" style="solid"]; - place_3c363836cf4e16666669a25da280a1865c2d2874 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="My custom transition label 3" style="solid" fontcolor="Grey" color="Red"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label="My custom transition +label 3" style="solid" fontcolor="Grey" color="Red"]; place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label="t2" style="solid" color="Blue"]; place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> place_3c363836cf4e16666669a25da280a1865c2d2874 [label="t3" style="solid"]; } diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index bf254f0..76c0510 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -127,8 +127,13 @@ private function createComplexStateMachineDefinition() $transitions[] = new Transition('t3', 'b', 'd'); $transitionsMetadata = new \SplObjectStorage(); + // PHP 7.2 doesn't allow this heredoc syntax in an array, use a dedicated variable instead + $label = <<<'EOTXT' +My custom transition +label 3 +EOTXT; $transitionsMetadata[$transitionWithMetadataDumpStyle] = [ - 'label' => 'My custom transition label 3', + 'label' => $label, 'color' => 'Grey', 'arrow_color' => 'Red', ]; From 96ec1843a27c7d3574bd521b807aa18101e31764 Mon Sep 17 00:00:00 2001 From: Alexis Lefebvre Date: Thu, 23 Feb 2023 19:51:51 +0100 Subject: [PATCH 077/145] fix style of label containing new lines in PUML dump --- Dumper/PlantUmlDumper.php | 8 +++++++- .../puml/arrow/complex-state-machine-marking.puml | 2 +- .../puml/arrow/complex-state-machine-nomarking.puml | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index f9a9957..d854846 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -217,11 +217,17 @@ private function getState(string $place, Definition $definition, Marking $markin private function getTransitionEscapedWithStyle(MetadataStoreInterface $workflowMetadata, Transition $transition, string $to): string { $to = $workflowMetadata->getMetadata('label', $transition) ?? $to; - $to = str_replace("\n", ' ', $to); + // Change new lines symbols to actual '\n' string, + // PUML will render them as new lines + $to = str_replace("\n", '\n', $to); $color = $workflowMetadata->getMetadata('color', $transition) ?? null; if (null !== $color) { + // Close and open before and after every '\n' string, + // so that the style is applied properly on every line + $to = str_replace('\n', sprintf('\n', $color), $to); + $to = sprintf( '%2$s', $color, diff --git a/Tests/fixtures/puml/arrow/complex-state-machine-marking.puml b/Tests/fixtures/puml/arrow/complex-state-machine-marking.puml index 59f8309..7f1367f 100644 --- a/Tests/fixtures/puml/arrow/complex-state-machine-marking.puml +++ b/Tests/fixtures/puml/arrow/complex-state-machine-marking.puml @@ -15,7 +15,7 @@ state "b" state "c" <> state "d" "a" --> "b": "t1" -"d" -[#Red]-> "b": "My custom transition label 3" +"d" -[#Red]-> "b": "My custom transition\nlabel 3" "b" -[#Blue]-> "c": "t2" "b" --> "d": "t3" @enduml diff --git a/Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml b/Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml index f3549c6..9d65314 100644 --- a/Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml +++ b/Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml @@ -15,7 +15,7 @@ state "b" state "c" state "d" "a" --> "b": "t1" -"d" -[#Red]-> "b": "My custom transition label 3" +"d" -[#Red]-> "b": "My custom transition\nlabel 3" "b" -[#Blue]-> "c": "t2" "b" --> "d": "t3" @enduml From ea55e454fe86fd36541e060c6fb64a430a19d27c Mon Sep 17 00:00:00 2001 From: Guillaume VDP Date: Mon, 27 Mar 2023 14:27:54 +0200 Subject: [PATCH 078/145] [Workflow] Adding more return types --- Event/Event.php | 3 +++ MarkingStore/MarkingStoreInterface.php | 2 ++ Metadata/GetMetadataTrait.php | 3 +++ Metadata/MetadataStoreInterface.php | 2 ++ Tests/Dumper/GraphvizDumperTest.php | 12 ++++++------ Tests/Dumper/MermaidDumperTest.php | 2 +- Tests/Dumper/PlantUmlDumperTest.php | 6 +++--- Tests/EventListener/GuardListenerTest.php | 4 ++-- Tests/MarkingStore/MethodMarkingStoreTest.php | 2 +- Tests/RegistryTest.php | 3 ++- Tests/Subject.php | 4 ++-- .../InstanceOfSupportStrategyTest.php | 3 ++- Tests/WorkflowBuilderTrait.php | 8 ++++---- Tests/WorkflowTest.php | 6 ++++-- Validator/DefinitionValidatorInterface.php | 2 ++ 15 files changed, 39 insertions(+), 23 deletions(-) diff --git a/Event/Event.php b/Event/Event.php index cd7fab7..5cd31e9 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -75,6 +75,9 @@ public function getWorkflowName() return $this->workflow->getName(); } + /** + * @return mixed + */ public function getMetadata(string $key, string|Transition|null $subject) { return $this->workflow->getMetadataStore()->getMetadata($key, $subject); diff --git a/MarkingStore/MarkingStoreInterface.php b/MarkingStore/MarkingStoreInterface.php index 99cb72c..7547a7f 100644 --- a/MarkingStore/MarkingStoreInterface.php +++ b/MarkingStore/MarkingStoreInterface.php @@ -31,6 +31,8 @@ public function getMarking(object $subject): Marking; /** * Sets a Marking to a subject. + * + * @return void */ public function setMarking(object $subject, Marking $marking, array $context = []); } diff --git a/Metadata/GetMetadataTrait.php b/Metadata/GetMetadataTrait.php index 9eddfd8..286e2f8 100644 --- a/Metadata/GetMetadataTrait.php +++ b/Metadata/GetMetadataTrait.php @@ -18,6 +18,9 @@ */ trait GetMetadataTrait { + /** + * @return mixed + */ public function getMetadata(string $key, string|Transition $subject = null) { if (null === $subject) { diff --git a/Metadata/MetadataStoreInterface.php b/Metadata/MetadataStoreInterface.php index 8b2886f..9acd540 100644 --- a/Metadata/MetadataStoreInterface.php +++ b/Metadata/MetadataStoreInterface.php @@ -34,6 +34,8 @@ public function getTransitionMetadata(Transition $transition): array; * @param string|Transition|null $subject Use null to get workflow metadata * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata + * + * @return mixed */ public function getMetadata(string $key, string|Transition $subject = null); } diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index f18868f..ff38822 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -47,7 +47,7 @@ public function testDumpWithMarking($definition, $marking, $expected) $this->assertEquals($expected, $dump); } - public static function provideWorkflowDefinitionWithMarking() + public static function provideWorkflowDefinitionWithMarking(): \Generator { yield [ self::createComplexWorkflowDefinition(), @@ -62,13 +62,13 @@ public static function provideWorkflowDefinitionWithMarking() ]; } - public static function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking(): \Generator { yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarking()]; yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarking()]; } - public static function createComplexWorkflowDefinitionDumpWithMarking() + public static function createComplexWorkflowDefinitionDumpWithMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -106,7 +106,7 @@ public static function createComplexWorkflowDefinitionDumpWithMarking() '; } - public static function createSimpleWorkflowDumpWithMarking() + public static function createSimpleWorkflowDumpWithMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -126,7 +126,7 @@ public static function createSimpleWorkflowDumpWithMarking() '; } - public static function provideComplexWorkflowDumpWithoutMarking() + public static function provideComplexWorkflowDumpWithoutMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" @@ -164,7 +164,7 @@ public static function provideComplexWorkflowDumpWithoutMarking() '; } - public static function provideSimpleWorkflowDumpWithoutMarking() + public static function provideSimpleWorkflowDumpWithoutMarking(): string { return 'digraph workflow { ratio="compress" rankdir="LR" diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php index 93c1e33..01b5638 100644 --- a/Tests/Dumper/MermaidDumperTest.php +++ b/Tests/Dumper/MermaidDumperTest.php @@ -146,7 +146,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): array ]; } - public static function provideWorkflowWithReservedWords() + public static function provideWorkflowWithReservedWords(): array { $builder = new DefinitionBuilder(); diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 6af7809..6e1b303 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -36,7 +36,7 @@ public function testDumpWorkflowWithoutMarking($definition, $marking, $expectedF $this->assertStringEqualsFile($file, $dump); } - public static function provideWorkflowDefinitionWithoutMarking() + public static function provideWorkflowDefinitionWithoutMarking(): \Generator { yield [self::createSimpleWorkflowDefinition(), null, 'simple-workflow-nomarking', 'SimpleDiagram']; yield [self::createComplexWorkflowDefinition(), null, 'complex-workflow-nomarking', 'ComplexDiagram']; @@ -59,7 +59,7 @@ public function testDumpStateMachineWithoutMarking($definition, $marking, $expec $this->assertStringEqualsFile($file, $dump); } - public static function provideStateMachineDefinitionWithoutMarking() + public static function provideStateMachineDefinitionWithoutMarking(): \Generator { yield [static::createComplexStateMachineDefinition(), null, 'complex-state-machine-nomarking', 'SimpleDiagram']; $marking = new Marking(['c' => 1, 'e' => 1]); @@ -94,7 +94,7 @@ public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() $this->assertStringEqualsFile($file, $dump); } - private function getFixturePath($name, $transitionType) + private function getFixturePath($name, $transitionType): string { return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml'; } diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index 26004df..a5ff6a0 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -143,7 +143,7 @@ public function testGuardExpressionBlocks() $this->assertTrue($event->isBlocked()); } - private function createEvent(Transition $transition = null) + private function createEvent(Transition $transition = null): GuardEvent { $subject = new Subject(); $transition ??= new Transition('name', 'from', 'to'); @@ -171,7 +171,7 @@ private function configureAuthenticationChecker($isUsed, $granted = true) ; } - private function configureValidator($isUsed, $valid = true) + private function configureValidator($isUsed, $valid = true): void { if (!$isUsed) { $this->validator diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 4691e11..34dbd3b 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -111,7 +111,7 @@ public function testGetMarkingWithUninitializedProperty2() $markingStore->getMarking($subject); } - private function createValueObject(string $markingValue) + private function createValueObject(string $markingValue): object { return new class($markingValue) { /** @var string */ diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index cf3ee8c..eb9e83b 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\Tests; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Exception\InvalidArgumentException; @@ -108,7 +109,7 @@ public function testAllWithNoMatch() $this->assertCount(0, $workflows); } - private function createWorkflowSupportStrategy($supportedClassName) + private function createWorkflowSupportStrategy($supportedClassName): MockObject&WorkflowSupportStrategyInterface { $strategy = $this->createMock(WorkflowSupportStrategyInterface::class); $strategy->expects($this->any())->method('supports') diff --git a/Tests/Subject.php b/Tests/Subject.php index dd63da9..6dd76e1 100644 --- a/Tests/Subject.php +++ b/Tests/Subject.php @@ -22,12 +22,12 @@ public function __construct($marking = null) $this->context = []; } - public function getMarking() + public function getMarking(): string|array|null { return $this->marking; } - public function setMarking($marking, array $context = []) + public function setMarking($marking, array $context = []): void { $this->marking = $marking; $this->context = $context; diff --git a/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php b/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php index 8a5c300..48a455e 100644 --- a/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php +++ b/Tests/SupportStrategy/InstanceOfSupportStrategyTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\Tests\SupportStrategy; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy; use Symfony\Component\Workflow\Workflow; @@ -31,7 +32,7 @@ public function testSupportsIfNotClassInstance() $this->assertFalse($strategy->supports($this->createWorkflow(), new Subject1())); } - private function createWorkflow() + private function createWorkflow(): MockObject&Workflow { return $this->getMockBuilder(Workflow::class) ->disableOriginalConstructor() diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index ae81232..07a589e 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -17,7 +17,7 @@ trait WorkflowBuilderTrait { - private static function createComplexWorkflowDefinition() + private static function createComplexWorkflowDefinition(): Definition { $places = range('a', 'g'); @@ -52,7 +52,7 @@ private static function createComplexWorkflowDefinition() // +----+ +----+ +----+ +----+ } - private static function createSimpleWorkflowDefinition() + private static function createSimpleWorkflowDefinition(): Definition { $places = range('a', 'c'); @@ -87,7 +87,7 @@ private static function createSimpleWorkflowDefinition() // +---+ +----+ +---+ +----+ +---+ } - private static function createWorkflowWithSameNameTransition() + private static function createWorkflowWithSameNameTransition(): Definition { $places = range('a', 'c'); @@ -115,7 +115,7 @@ private static function createWorkflowWithSameNameTransition() // +--------------------------------------------------------------------+ } - private static function createComplexStateMachineDefinition() + private static function createComplexStateMachineDefinition(): Definition { $places = ['a', 'b', 'c', 'd']; diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index d5f81b5..2b3d1ec 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -427,14 +427,16 @@ public function testApplyWithEventDispatcher() $this->assertSame($eventNameExpected, $eventDispatcher->dispatchedEvents); } - public static function provideApplyWithEventDispatcherForAnnounceTests() + public static function provideApplyWithEventDispatcherForAnnounceTests(): \Generator { yield [false, [Workflow::DISABLE_ANNOUNCE_EVENT => true]]; yield [true, [Workflow::DISABLE_ANNOUNCE_EVENT => false]]; yield [true, []]; } - /** @dataProvider provideApplyWithEventDispatcherForAnnounceTests */ + /** + * @dataProvider provideApplyWithEventDispatcherForAnnounceTests + */ public function testApplyWithEventDispatcherForAnnounce(bool $fired, array $context) { $definition = $this->createComplexWorkflowDefinition(); diff --git a/Validator/DefinitionValidatorInterface.php b/Validator/DefinitionValidatorInterface.php index f02f582..c9717b7 100644 --- a/Validator/DefinitionValidatorInterface.php +++ b/Validator/DefinitionValidatorInterface.php @@ -21,6 +21,8 @@ interface DefinitionValidatorInterface { /** + * @return void + * * @throws InvalidDefinitionException on invalid definition */ public function validate(Definition $definition, string $name); From a92832971c45b721a26b295bb6b0b1a9323a670c Mon Sep 17 00:00:00 2001 From: Artyum Petrov <17199757+artyuum@users.noreply.github.com> Date: Thu, 20 Apr 2023 19:24:19 +0400 Subject: [PATCH 079/145] Add "composer require..." in all exception messages when needed --- EventListener/ExpressionLanguage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index ab5ea51..82fe165 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -33,7 +33,7 @@ protected function registerFunctions() $this->register('is_valid', fn ($object = 'null', $groups = 'null') => sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { - throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed.'); + throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed. Try running "composer require symfony/validator".'); } $errors = $variables['validator']->validate($object, null, $groups); From 8ef4df7a9f5ca513d93955fbf1275506dca519c7 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:38:00 +0200 Subject: [PATCH 080/145] [6.4] Allow 7.0 deps --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 6b23eeb..b70ab48 100644 --- a/composer.json +++ b/composer.json @@ -24,11 +24,11 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/dependency-injection": "^5.4|^6.0", - "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/expression-language": "^5.4|^6.0", - "symfony/security-core": "^5.4|^6.0", - "symfony/validator": "^5.4|^6.0" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0" }, "conflict": { "symfony/event-dispatcher": "<5.4" From d1fa644af5127ba3ba0560aa28f864e3359486da Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 081/145] [7.0] Bump to PHP 8.2 minimum --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index b70ab48..8a9e7aa 100644 --- a/composer.json +++ b/composer.json @@ -20,18 +20,18 @@ } ], "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/security-core": "^5.4|^6.0|^7.0", - "symfony/validator": "^5.4|^6.0|^7.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0" }, "conflict": { - "symfony/event-dispatcher": "<5.4" + "symfony/event-dispatcher": "<6.4" }, "autoload": { "psr-4": { "Symfony\\Component\\Workflow\\": "" }, From 6b59f91c6b12d2bb479da8aebebb024fee4b3255 Mon Sep 17 00:00:00 2001 From: Louis-Proffit <77028679+Louis-Proffit@users.noreply.github.com> Date: Fri, 9 Jun 2023 09:25:48 +0200 Subject: [PATCH 082/145] [FrameworkBundle][Workflow] Add metadata dumping support for GraphvizDumper --- CHANGELOG.md | 6 + Dumper/GraphvizDumper.php | 110 ++++++++++++++++--- Dumper/StateMachineGraphvizDumper.php | 13 ++- Tests/Dumper/GraphvizDumperTest.php | 152 ++++++++++++++++++++++++-- 4 files changed, 254 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 808ad77..ff71628 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ CHANGELOG ========= +6.4 +--- + + * Add `with-metadata` option to the `workflow:dump` command to include places, + transitions and workflow's metadata into dumped graph + 6.2 --- diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 01a5b36..0521a14 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -44,15 +44,19 @@ class GraphvizDumper implements DumperInterface */ public function dump(Definition $definition, Marking $marking = null, array $options = []): string { - $places = $this->findPlaces($definition, $marking); - $transitions = $this->findTransitions($definition); + $withMetadata = $options['with-metadata'] ?? false; + + $places = $this->findPlaces($definition, $withMetadata, $marking); + $transitions = $this->findTransitions($definition, $withMetadata); $edges = $this->findEdges($definition); $options = array_replace_recursive(self::$defaultOptions, $options); - return $this->startDot($options) - .$this->addPlaces($places) - .$this->addTransitions($transitions) + $label = $this->formatLabel($definition, $withMetadata, $options); + + return $this->startDot($options, $label) + .$this->addPlaces($places, $withMetadata) + .$this->addTransitions($transitions, $withMetadata) .$this->addEdges($edges) .$this->endDot(); } @@ -60,7 +64,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt /** * @internal */ - protected function findPlaces(Definition $definition, Marking $marking = null): array + protected function findPlaces(Definition $definition, bool $withMetadata, Marking $marking = null): array { $workflowMetadata = $definition->getMetadataStore(); @@ -80,9 +84,16 @@ protected function findPlaces(Definition $definition, Marking $marking = null): $attributes['style'] = 'filled'; $attributes['fillcolor'] = $backgroundColor; } + if ($withMetadata) { + $attributes['metadata'] = $workflowMetadata->getPlaceMetadata($place); + } $label = $workflowMetadata->getMetadata('label', $place); if (null !== $label) { $attributes['name'] = $label; + if ($withMetadata) { + // Don't include label in metadata if already used as name + unset($attributes['metadata']['label']); + } } $places[$place] = [ 'attributes' => $attributes, @@ -95,7 +106,7 @@ protected function findPlaces(Definition $definition, Marking $marking = null): /** * @internal */ - protected function findTransitions(Definition $definition): array + protected function findTransitions(Definition $definition, bool $withMetadata): array { $workflowMetadata = $definition->getMetadataStore(); @@ -111,9 +122,16 @@ protected function findTransitions(Definition $definition): array } $name = $workflowMetadata->getMetadata('label', $transition) ?? $transition->getName(); + $metadata = []; + if ($withMetadata) { + $metadata = $workflowMetadata->getTransitionMetadata($transition); + unset($metadata['label']); + } + $transitions[] = [ 'attributes' => $attributes, 'name' => $name, + 'metadata' => $metadata, ]; } @@ -123,7 +141,7 @@ protected function findTransitions(Definition $definition): array /** * @internal */ - protected function addPlaces(array $places): string + protected function addPlaces(array $places, float $withMetadata): string { $code = ''; @@ -135,7 +153,15 @@ protected function addPlaces(array $places): string $placeName = $id; } - $code .= sprintf(" place_%s [label=\"%s\", shape=circle%s];\n", $this->dotize($id), $this->escape($placeName), $this->addAttributes($place['attributes'])); + if ($withMetadata) { + $escapedLabel = sprintf('<%s%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); + // Don't include metadata in default attributes used to format the place + unset($place['attributes']['metadata']); + } else { + $escapedLabel = sprintf('"%s"', $this->escape($placeName)); + } + + $code .= sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -144,12 +170,18 @@ protected function addPlaces(array $places): string /** * @internal */ - protected function addTransitions(array $transitions): string + protected function addTransitions(array $transitions, bool $withMetadata): string { $code = ''; foreach ($transitions as $i => $place) { - $code .= sprintf(" transition_%s [label=\"%s\",%s];\n", $this->dotize($i), $this->escape($place['name']), $this->addAttributes($place['attributes'])); + if ($withMetadata) { + $escapedLabel = sprintf('<%s%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); + } else { + $escapedLabel = '"'.$this->escape($place['name']).'"'; + } + + $code .= sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -215,10 +247,11 @@ protected function addEdges(array $edges): string /** * @internal */ - protected function startDot(array $options): string + protected function startDot(array $options, string $label): string { - return sprintf("digraph workflow {\n %s\n node [%s];\n edge [%s];\n\n", + return sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", $this->addOptions($options['graph']), + '""' !== $label && '<>' !== $label ? sprintf(' label=%s', $label) : '', $this->addOptions($options['node']), $this->addOptions($options['edge']) ); @@ -248,6 +281,9 @@ protected function escape(string|bool $value): string return \is_bool($value) ? ($value ? '1' : '0') : addslashes($value); } + /** + * @internal + */ protected function addAttributes(array $attributes): string { $code = []; @@ -259,6 +295,33 @@ protected function addAttributes(array $attributes): string return $code ? ' '.implode(' ', $code) : ''; } + /** + * Handles the label of the graph depending on whether a label was set in CLI, + * if metadata should be included and if there are any. + * + * The produced label must be escaped. + * + * @internal + */ + protected function formatLabel(Definition $definition, string $withMetadata, array $options): string + { + $currentLabel = $options['label'] ?? ''; + + if (!$withMetadata) { + // Only currentLabel to handle. If null, will be translated to empty string + return sprintf('"%s"', $this->escape($currentLabel)); + } + $workflowMetadata = $definition->getMetadataStore()->getWorkflowMetadata(); + + if ('' === $currentLabel) { + // Only metadata to handle + return sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); + } + + // currentLabel and metadata to handle + return sprintf('<%s%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); + } + private function addOptions(array $options): string { $code = []; @@ -269,4 +332,25 @@ private function addOptions(array $options): string return implode(' ', $code); } + + /** + * @param bool $lineBreakFirstIfNotEmpty Whether to add a separator in the first place when metadata is not empty + */ + private function addMetadata(array $metadata, bool $lineBreakFirstIfNotEmpty = true): string + { + $code = []; + + $skipSeparator = !$lineBreakFirstIfNotEmpty; + + foreach ($metadata as $key => $value) { + if ($skipSeparator) { + $code[] = sprintf('%s: %s', $this->escape($key), $this->escape($value)); + $skipSeparator = false; + } else { + $code[] = sprintf('%s%s: %s', '
', $this->escape($key), $this->escape($value)); + } + } + + return $code ? implode('', $code) : ''; + } } diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index ac90b61..a7fda86 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -27,16 +27,19 @@ class StateMachineGraphvizDumper extends GraphvizDumper */ public function dump(Definition $definition, Marking $marking = null, array $options = []): string { - $places = $this->findPlaces($definition, $marking); + $withMetadata = $options['with-metadata'] ?? false; + + $places = $this->findPlaces($definition, $withMetadata, $marking); $edges = $this->findEdges($definition); $options = array_replace_recursive(self::$defaultOptions, $options); - return $this->startDot($options) - .$this->addPlaces($places) + $label = $this->formatLabel($definition, $withMetadata, $options); + + return $this->startDot($options, $label) + .$this->addPlaces($places, $withMetadata) .$this->addEdges($edges) - .$this->endDot() - ; + .$this->endDot(); } /** diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index ff38822..aa4b59e 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -30,9 +30,9 @@ protected function setUp(): void /** * @dataProvider provideWorkflowDefinitionWithoutMarking */ - public function testDumpWithoutMarking($definition, $expected) + public function testDumpWithoutMarking($definition, $expected, $withMetadata) { - $dump = $this->dumper->dump($definition); + $dump = $this->dumper->dump($definition, null, ['with-metadata' => $withMetadata]); $this->assertEquals($expected, $dump); } @@ -40,32 +40,50 @@ public function testDumpWithoutMarking($definition, $expected) /** * @dataProvider provideWorkflowDefinitionWithMarking */ - public function testDumpWithMarking($definition, $marking, $expected) + public function testDumpWithMarking($definition, $marking, $expected, $withMetadata) { - $dump = $this->dumper->dump($definition, $marking); + $dump = $this->dumper->dump($definition, $marking, ['with-metadata' => $withMetadata]); $this->assertEquals($expected, $dump); } + public static function provideWorkflowDefinitionWithoutMarking(): \Generator + { + yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarking(), false]; + yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarking(), false]; + yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarkingWithMetadata(), true]; + yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarkingWithMetadata(), true]; + } + public static function provideWorkflowDefinitionWithMarking(): \Generator { yield [ self::createComplexWorkflowDefinition(), new Marking(['b' => 1]), self::createComplexWorkflowDefinitionDumpWithMarking(), + false, ]; yield [ self::createSimpleWorkflowDefinition(), new Marking(['c' => 1, 'd' => 1]), self::createSimpleWorkflowDumpWithMarking(), + false, ]; - } - public static function provideWorkflowDefinitionWithoutMarking(): \Generator - { - yield [self::createComplexWorkflowDefinition(), self::provideComplexWorkflowDumpWithoutMarking()]; - yield [self::createSimpleWorkflowDefinition(), self::provideSimpleWorkflowDumpWithoutMarking()]; + yield [ + self::createComplexWorkflowDefinition(), + new Marking(['b' => 1]), + self::createComplexWorkflowDefinitionDumpWithMarkingAndMetadata(), + true, + ]; + + yield [ + self::createSimpleWorkflowDefinition(), + new Marking(['c' => 1, 'd' => 1]), + self::createSimpleWorkflowDumpWithMarkingAndMetadata(), + true, + ]; } public static function createComplexWorkflowDefinitionDumpWithMarking(): string @@ -106,6 +124,44 @@ public static function createComplexWorkflowDefinitionDumpWithMarking(): string '; } + public static function createComplexWorkflowDefinitionDumpWithMarkingAndMetadata(): string + { + return 'digraph workflow { + ratio="compress" rankdir="LR" + node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="false" width="1"]; + edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; + + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 [label=<a>, shape=circle style="filled"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label=<b>, shape=circle color="#FF0000" shape="doublecircle"]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label=<c>, shape=circle]; + place_3c363836cf4e16666669a25da280a1865c2d2874 [label=<d>, shape=circle]; + place_58e6b3a414a1e090dfc6029add0f3555ccba127f [label=<e>, shape=circle]; + place_4a0a19218e082a343a1b17e5333409af9d98f0f5 [label=<f>, shape=circle]; + place_54fd1711209fb1c0781092374132c66e79e2241b [label=<g>, shape=circle]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [label=<t1>, shape="box" regular="1"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab [label=<t2>, shape="box" regular="1"]; + transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 [label=<My custom transition label 1
color: Red
arrow_color: Green>, shape="box" regular="1"]; + transition_77de68daecd823babbb58edb1c8e14d7106e83bb [label=<t4>, shape="box" regular="1"]; + transition_1b6453892473a467d07372d45eb05abc2031647a [label=<t5>, shape="box" regular="1"]; + transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 [label=<t6>, shape="box" regular="1"]; + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [style="solid"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab -> place_3c363836cf4e16666669a25da280a1865c2d2874 [style="solid"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 [style="solid"]; + transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 -> place_58e6b3a414a1e090dfc6029add0f3555ccba127f [style="solid"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> transition_77de68daecd823babbb58edb1c8e14d7106e83bb [style="solid"]; + transition_77de68daecd823babbb58edb1c8e14d7106e83bb -> place_4a0a19218e082a343a1b17e5333409af9d98f0f5 [style="solid"]; + place_58e6b3a414a1e090dfc6029add0f3555ccba127f -> transition_1b6453892473a467d07372d45eb05abc2031647a [style="solid"]; + transition_1b6453892473a467d07372d45eb05abc2031647a -> place_54fd1711209fb1c0781092374132c66e79e2241b [style="solid"]; + place_4a0a19218e082a343a1b17e5333409af9d98f0f5 -> transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 [style="solid"]; + transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 -> place_54fd1711209fb1c0781092374132c66e79e2241b [style="solid"]; +} +'; + } + public static function createSimpleWorkflowDumpWithMarking(): string { return 'digraph workflow { @@ -126,6 +182,26 @@ public static function createSimpleWorkflowDumpWithMarking(): string '; } + public static function createSimpleWorkflowDumpWithMarkingAndMetadata(): string + { + return 'digraph workflow { + ratio="compress" rankdir="LR" + node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="false" width="1"]; + edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; + + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 [label=<a>, shape=circle style="filled"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label=<b>, shape=circle]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label=<c
bg_color: DeepSkyBlue
description: My custom place description>, shape=circle color="#FF0000" shape="doublecircle" style="filled" fillcolor="DeepSkyBlue"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [label=<My custom transition label 2
color: Grey
arrow_color: Purple>, shape="box" regular="1"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab [label=<t2
arrow_color: Pink>, shape="box" regular="1"]; + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [style="solid"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [style="solid"]; +} +'; + } + public static function provideComplexWorkflowDumpWithoutMarking(): string { return 'digraph workflow { @@ -164,6 +240,44 @@ public static function provideComplexWorkflowDumpWithoutMarking(): string '; } + public static function provideComplexWorkflowDumpWithoutMarkingWithMetadata(): string + { + return 'digraph workflow { + ratio="compress" rankdir="LR" + node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="false" width="1"]; + edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; + + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 [label=<a>, shape=circle style="filled"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label=<b>, shape=circle]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label=<c>, shape=circle]; + place_3c363836cf4e16666669a25da280a1865c2d2874 [label=<d>, shape=circle]; + place_58e6b3a414a1e090dfc6029add0f3555ccba127f [label=<e>, shape=circle]; + place_4a0a19218e082a343a1b17e5333409af9d98f0f5 [label=<f>, shape=circle]; + place_54fd1711209fb1c0781092374132c66e79e2241b [label=<g>, shape=circle]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [label=<t1>, shape="box" regular="1"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab [label=<t2>, shape="box" regular="1"]; + transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 [label=<My custom transition label 1
color: Red
arrow_color: Green>, shape="box" regular="1"]; + transition_77de68daecd823babbb58edb1c8e14d7106e83bb [label=<t4>, shape="box" regular="1"]; + transition_1b6453892473a467d07372d45eb05abc2031647a [label=<t5>, shape="box" regular="1"]; + transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 [label=<t6>, shape="box" regular="1"]; + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [style="solid"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab -> place_3c363836cf4e16666669a25da280a1865c2d2874 [style="solid"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 [style="solid"]; + transition_da4b9237bacccdf19c0760cab7aec4a8359010b0 -> place_58e6b3a414a1e090dfc6029add0f3555ccba127f [style="solid"]; + place_3c363836cf4e16666669a25da280a1865c2d2874 -> transition_77de68daecd823babbb58edb1c8e14d7106e83bb [style="solid"]; + transition_77de68daecd823babbb58edb1c8e14d7106e83bb -> place_4a0a19218e082a343a1b17e5333409af9d98f0f5 [style="solid"]; + place_58e6b3a414a1e090dfc6029add0f3555ccba127f -> transition_1b6453892473a467d07372d45eb05abc2031647a [style="solid"]; + transition_1b6453892473a467d07372d45eb05abc2031647a -> place_54fd1711209fb1c0781092374132c66e79e2241b [style="solid"]; + place_4a0a19218e082a343a1b17e5333409af9d98f0f5 -> transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 [style="solid"]; + transition_ac3478d69a3c81fa62e60f5c3696165a4e5e6ac4 -> place_54fd1711209fb1c0781092374132c66e79e2241b [style="solid"]; +} +'; + } + public static function provideSimpleWorkflowDumpWithoutMarking(): string { return 'digraph workflow { @@ -181,6 +295,26 @@ public static function provideSimpleWorkflowDumpWithoutMarking(): string place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; transition_356a192b7913b04c54574d18c28d46e6395428ab -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [style="solid"]; } +'; + } + + public static function provideSimpleWorkflowDumpWithoutMarkingWithMetadata(): string + { + return 'digraph workflow { + ratio="compress" rankdir="LR" + node [fontsize="9" fontname="Arial" color="#333333" fillcolor="lightblue" fixedsize="false" width="1"]; + edge [fontsize="9" fontname="Arial" color="#333333" arrowhead="normal" arrowsize="0.5"]; + + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 [label=<a>, shape=circle style="filled"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [label=<b>, shape=circle]; + place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [label=<c
bg_color: DeepSkyBlue
description: My custom place description>, shape=circle style="filled" fillcolor="DeepSkyBlue"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [label=<My custom transition label 2
color: Grey
arrow_color: Purple>, shape="box" regular="1"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab [label=<t2
arrow_color: Pink>, shape="box" regular="1"]; + place_86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 -> transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c [style="solid"]; + transition_b6589fc6ab0dc82cf12099d1c2d40ab994e8410c -> place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 [style="solid"]; + place_e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 -> transition_356a192b7913b04c54574d18c28d46e6395428ab [style="solid"]; + transition_356a192b7913b04c54574d18c28d46e6395428ab -> place_84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 [style="solid"]; +} '; } } From 3bd6c35a2f610f11e1bb299b3698618555a54187 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 4 Jul 2023 14:50:59 +0200 Subject: [PATCH 083/145] [7.0] Remove remaining deprecated code paths --- CHANGELOG.md | 5 +++++ Definition.php | 5 +---- Registry.php | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff71628..bec98b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.0 +--- + + * Require explicit argument when calling `Definition::setInitialPlaces()` + 6.4 --- diff --git a/Definition.php b/Definition.php index cdb1809..91172dc 100644 --- a/Definition.php +++ b/Definition.php @@ -76,11 +76,8 @@ public function getMetadataStore(): MetadataStoreInterface return $this->metadataStore; } - private function setInitialPlaces(string|array $places = null): void + private function setInitialPlaces(string|array|null $places): void { - if (1 > \func_num_args()) { - trigger_deprecation('symfony/workflow', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__); - } if (!$places) { return; } diff --git a/Registry.php b/Registry.php index 287d8b7..ad72693 100644 --- a/Registry.php +++ b/Registry.php @@ -18,7 +18,7 @@ * @author Fabien Potencier * @author Grégoire Pineau * - * @internal since Symfony 6.2. Inject the workflow where you need it. + * @internal */ class Registry { From 2a11e36acc082bc122a6d1fb42f10fee97d3ee2a Mon Sep 17 00:00:00 2001 From: Wouter de Jong Date: Sun, 2 Jul 2023 23:52:21 +0200 Subject: [PATCH 084/145] [Components] Convert to native return types --- Event/Event.php | 25 +++++----------------- EventListener/AuditTrailListener.php | 15 +++---------- EventListener/ExpressionLanguage.php | 5 +---- EventListener/GuardExpression.php | 10 ++------- EventListener/GuardListener.php | 5 +---- Exception/TransitionException.php | 5 +---- Marking.php | 20 ++++------------- MarkingStore/MarkingStoreInterface.php | 4 +--- Metadata/GetMetadataTrait.php | 5 +---- Metadata/MetadataStoreInterface.php | 4 +--- Registry.php | 5 +---- Validator/DefinitionValidatorInterface.php | 4 +--- Validator/StateMachineValidator.php | 5 +---- Validator/WorkflowValidator.php | 5 +---- 14 files changed, 24 insertions(+), 93 deletions(-) diff --git a/Event/Event.php b/Event/Event.php index 5cd31e9..524c4f1 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -38,26 +38,17 @@ public function __construct(object $subject, Marking $marking, Transition $trans $this->context = $context; } - /** - * @return Marking - */ - public function getMarking() + public function getMarking(): Marking { return $this->marking; } - /** - * @return object - */ - public function getSubject() + public function getSubject(): object { return $this->subject; } - /** - * @return Transition|null - */ - public function getTransition() + public function getTransition(): ?Transition { return $this->transition; } @@ -67,18 +58,12 @@ public function getWorkflow(): WorkflowInterface return $this->workflow; } - /** - * @return string - */ - public function getWorkflowName() + public function getWorkflowName(): string { return $this->workflow->getName(); } - /** - * @return mixed - */ - public function getMetadata(string $key, string|Transition|null $subject) + public function getMetadata(string $key, string|Transition|null $subject): mixed { return $this->workflow->getMetadataStore()->getMetadata($key, $subject); } diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 8d82824..b5e2734 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -27,28 +27,19 @@ public function __construct(LoggerInterface $logger) $this->logger = $logger; } - /** - * @return void - */ - public function onLeave(Event $event) + public function onLeave(Event $event): void { foreach ($event->getTransition()->getFroms() as $place) { $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } - /** - * @return void - */ - public function onTransition(Event $event) + public function onTransition(Event $event): void { $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } - /** - * @return void - */ - public function onEnter(Event $event) + public function onEnter(Event $event): void { foreach ($event->getTransition()->getTos() as $place) { $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index 82fe165..a70e5f7 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -22,10 +22,7 @@ */ class ExpressionLanguage extends BaseExpressionLanguage { - /** - * @return void - */ - protected function registerFunctions() + protected function registerFunctions(): void { parent::registerFunctions(); diff --git a/EventListener/GuardExpression.php b/EventListener/GuardExpression.php index 23e830c..27bf8dc 100644 --- a/EventListener/GuardExpression.php +++ b/EventListener/GuardExpression.php @@ -24,18 +24,12 @@ public function __construct(Transition $transition, string $expression) $this->expression = $expression; } - /** - * @return Transition - */ - public function getTransition() + public function getTransition(): Transition { return $this->transition; } - /** - * @return string - */ - public function getExpression() + public function getExpression(): string { return $this->expression; } diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index c207b1a..1c30ac1 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -43,10 +43,7 @@ public function __construct(array $configuration, ExpressionLanguage $expression $this->validator = $validator; } - /** - * @return void - */ - public function onTransition(GuardEvent $event, string $eventName) + public function onTransition(GuardEvent $event, string $eventName): void { if (!isset($this->configuration[$eventName])) { return; diff --git a/Exception/TransitionException.php b/Exception/TransitionException.php index 890d8e2..0cf1afb 100644 --- a/Exception/TransitionException.php +++ b/Exception/TransitionException.php @@ -34,10 +34,7 @@ public function __construct(object $subject, string $transitionName, WorkflowInt $this->context = $context; } - /** - * @return object - */ - public function getSubject() + public function getSubject(): object { return $this->subject; } diff --git a/Marking.php b/Marking.php index 95a83f0..de9c8a8 100644 --- a/Marking.php +++ b/Marking.php @@ -31,34 +31,22 @@ public function __construct(array $representation = []) } } - /** - * @return void - */ - public function mark(string $place) + public function mark(string $place): void { $this->places[$place] = 1; } - /** - * @return void - */ - public function unmark(string $place) + public function unmark(string $place): void { unset($this->places[$place]); } - /** - * @return bool - */ - public function has(string $place) + public function has(string $place): bool { return isset($this->places[$place]); } - /** - * @return array - */ - public function getPlaces() + public function getPlaces(): array { return $this->places; } diff --git a/MarkingStore/MarkingStoreInterface.php b/MarkingStore/MarkingStoreInterface.php index 7547a7f..43b34f5 100644 --- a/MarkingStore/MarkingStoreInterface.php +++ b/MarkingStore/MarkingStoreInterface.php @@ -31,8 +31,6 @@ public function getMarking(object $subject): Marking; /** * Sets a Marking to a subject. - * - * @return void */ - public function setMarking(object $subject, Marking $marking, array $context = []); + public function setMarking(object $subject, Marking $marking, array $context = []): void; } diff --git a/Metadata/GetMetadataTrait.php b/Metadata/GetMetadataTrait.php index 286e2f8..b3b2f29 100644 --- a/Metadata/GetMetadataTrait.php +++ b/Metadata/GetMetadataTrait.php @@ -18,10 +18,7 @@ */ trait GetMetadataTrait { - /** - * @return mixed - */ - public function getMetadata(string $key, string|Transition $subject = null) + public function getMetadata(string $key, string|Transition $subject = null): mixed { if (null === $subject) { return $this->getWorkflowMetadata()[$key] ?? null; diff --git a/Metadata/MetadataStoreInterface.php b/Metadata/MetadataStoreInterface.php index 9acd540..ff1b31e 100644 --- a/Metadata/MetadataStoreInterface.php +++ b/Metadata/MetadataStoreInterface.php @@ -34,8 +34,6 @@ public function getTransitionMetadata(Transition $transition): array; * @param string|Transition|null $subject Use null to get workflow metadata * Use a string (the place name) to get place metadata * Use a Transition instance to get transition metadata - * - * @return mixed */ - public function getMetadata(string $key, string|Transition $subject = null); + public function getMetadata(string $key, string|Transition $subject = null): mixed; } diff --git a/Registry.php b/Registry.php index ad72693..592d64a 100644 --- a/Registry.php +++ b/Registry.php @@ -24,10 +24,7 @@ class Registry { private array $workflows = []; - /** - * @return void - */ - public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategyInterface $supportStrategy) + public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategyInterface $supportStrategy): void { $this->workflows[] = [$workflow, $supportStrategy]; } diff --git a/Validator/DefinitionValidatorInterface.php b/Validator/DefinitionValidatorInterface.php index c9717b7..7944a05 100644 --- a/Validator/DefinitionValidatorInterface.php +++ b/Validator/DefinitionValidatorInterface.php @@ -21,9 +21,7 @@ interface DefinitionValidatorInterface { /** - * @return void - * * @throws InvalidDefinitionException on invalid definition */ - public function validate(Definition $definition, string $name); + public function validate(Definition $definition, string $name): void; } diff --git a/Validator/StateMachineValidator.php b/Validator/StateMachineValidator.php index 20afc8d..521fc88 100644 --- a/Validator/StateMachineValidator.php +++ b/Validator/StateMachineValidator.php @@ -19,10 +19,7 @@ */ class StateMachineValidator implements DefinitionValidatorInterface { - /** - * @return void - */ - public function validate(Definition $definition, string $name) + public function validate(Definition $definition, string $name): void { $transitionFromNames = []; foreach ($definition->getTransitions() as $transition) { diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index c13c281..3f88d11 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -27,10 +27,7 @@ public function __construct(bool $singlePlace = false) $this->singlePlace = $singlePlace; } - /** - * @return void - */ - public function validate(Definition $definition, string $name) + public function validate(Definition $definition, string $name): void { // Make sure all transitions for one place has unique name. $places = array_fill_keys($definition->getPlaces(), []); From 22a5ea1c41d2db5375ab25d3675021c574fd685a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 20 Jun 2023 16:48:54 +0200 Subject: [PATCH 085/145] [DependencyInjection] Improve reporting named autowiring aliases --- WorkflowInterface.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WorkflowInterface.php b/WorkflowInterface.php index ec1ca5a..17aa7e0 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -16,6 +16,8 @@ use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; /** + * Describes a workflow instance. + * * @author Amrouche Hamza */ interface WorkflowInterface From cc8965ac3b336e072348bbe4fcff2d47c3be8b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Mon, 24 Jul 2023 17:12:13 +0200 Subject: [PATCH 086/145] [Workflow] Cleaning code --- Tests/MarkingStore/MethodMarkingStoreTest.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 0ba2254..d7d8cfa 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Workflow\Tests\MarkingStore; use PHPUnit\Framework\TestCase; -use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; use Symfony\Component\Workflow\Tests\Subject; @@ -26,7 +25,6 @@ public function testGetSetMarkingWithMultipleState() $marking = $markingStore->getMarking($subject); - $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(0, $marking->getPlaces()); $marking->mark('first_place'); @@ -48,7 +46,6 @@ public function testGetSetMarkingWithSingleState() $marking = $markingStore->getMarking($subject); - $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(0, $marking->getPlaces()); $marking->mark('first_place'); @@ -70,7 +67,6 @@ public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() $marking = $markingStore->getMarking($subject); - $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(1, $marking->getPlaces()); } @@ -82,7 +78,6 @@ public function testGetMarkingWithValueObject() $marking = $markingStore->getMarking($subject); - $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(1, $marking->getPlaces()); $this->assertSame('first_place', (string) $subject->getMarking()); } @@ -98,7 +93,6 @@ public function testGetMarkingWithUninitializedProperty() $marking = $markingStore->getMarking($subject); - $this->assertInstanceOf(Marking::class, $marking); $this->assertCount(0, $marking->getPlaces()); } From 8b6018f46484e3621cc46a7a044d1dd0f5d232ee Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:28:24 +0200 Subject: [PATCH 087/145] Use typed properties in tests as much as possible --- Tests/Dumper/GraphvizDumperTest.php | 11 ++--------- Tests/Dumper/StateMachineGraphvizDumperTest.php | 11 ++--------- Tests/EventListener/AuditTrailListenerTest.php | 2 +- Tests/EventListener/GuardListenerTest.php | 16 +++++----------- Tests/MarkingStore/MethodMarkingStoreTest.php | 3 +-- Tests/Metadata/InMemoryMetadataStoreTest.php | 4 ++-- Tests/RegistryTest.php | 7 +------ Tests/Subject.php | 5 ++--- Tests/WorkflowTest.php | 2 +- 9 files changed, 17 insertions(+), 44 deletions(-) diff --git a/Tests/Dumper/GraphvizDumperTest.php b/Tests/Dumper/GraphvizDumperTest.php index aa4b59e..9356715 100644 --- a/Tests/Dumper/GraphvizDumperTest.php +++ b/Tests/Dumper/GraphvizDumperTest.php @@ -20,19 +20,12 @@ class GraphvizDumperTest extends TestCase { use WorkflowBuilderTrait; - private $dumper; - - protected function setUp(): void - { - $this->dumper = new GraphvizDumper(); - } - /** * @dataProvider provideWorkflowDefinitionWithoutMarking */ public function testDumpWithoutMarking($definition, $expected, $withMetadata) { - $dump = $this->dumper->dump($definition, null, ['with-metadata' => $withMetadata]); + $dump = (new GraphvizDumper())->dump($definition, null, ['with-metadata' => $withMetadata]); $this->assertEquals($expected, $dump); } @@ -42,7 +35,7 @@ public function testDumpWithoutMarking($definition, $expected, $withMetadata) */ public function testDumpWithMarking($definition, $marking, $expected, $withMetadata) { - $dump = $this->dumper->dump($definition, $marking, ['with-metadata' => $withMetadata]); + $dump = (new GraphvizDumper())->dump($definition, $marking, ['with-metadata' => $withMetadata]); $this->assertEquals($expected, $dump); } diff --git a/Tests/Dumper/StateMachineGraphvizDumperTest.php b/Tests/Dumper/StateMachineGraphvizDumperTest.php index a45e07c..20b3694 100644 --- a/Tests/Dumper/StateMachineGraphvizDumperTest.php +++ b/Tests/Dumper/StateMachineGraphvizDumperTest.php @@ -20,18 +20,11 @@ class StateMachineGraphvizDumperTest extends TestCase { use WorkflowBuilderTrait; - private $dumper; - - protected function setUp(): void - { - $this->dumper = new StateMachineGraphvizDumper(); - } - public function testDumpWithoutMarking() { $definition = $this->createComplexStateMachineDefinition(); - $dump = $this->dumper->dump($definition); + $dump = (new StateMachineGraphvizDumper())->dump($definition); $expected = <<<'EOGRAPH' digraph workflow { @@ -79,7 +72,7 @@ public function testDumpWithMarking() EOGRAPH; - $dump = $this->dumper->dump($definition, $marking); + $dump = (new StateMachineGraphvizDumper())->dump($definition, $marking); $this->assertEquals($expected, $dump); } diff --git a/Tests/EventListener/AuditTrailListenerTest.php b/Tests/EventListener/AuditTrailListenerTest.php index f499833..53336ba 100644 --- a/Tests/EventListener/AuditTrailListenerTest.php +++ b/Tests/EventListener/AuditTrailListenerTest.php @@ -51,7 +51,7 @@ public function testItWorks() class Logger extends AbstractLogger { - public $logs = []; + public array $logs = []; public function log($level, $message, array $context = []): void { diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index a5ff6a0..776a3ee 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Workflow\Tests\EventListener; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; @@ -32,10 +33,10 @@ class GuardListenerTest extends TestCase { - private $authenticationChecker; - private $validator; - private $listener; - private $configuration; + private MockObject&AuthorizationCheckerInterface $authenticationChecker; + private MockObject&ValidatorInterface $validator; + private GuardListener $listener; + private array $configuration; protected function setUp(): void { @@ -58,13 +59,6 @@ protected function setUp(): void $this->listener = new GuardListener($this->configuration, $expressionLanguage, $tokenStorage, $this->authenticationChecker, $trustResolver, $roleHierarchy, $this->validator); } - protected function tearDown(): void - { - $this->authenticationChecker = null; - $this->validator = null; - $this->listener = null; - } - public function testWithNotSupportedEvent() { $event = $this->createEvent(); diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 34dbd3b..730e0f4 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -114,8 +114,7 @@ public function testGetMarkingWithUninitializedProperty2() private function createValueObject(string $markingValue): object { return new class($markingValue) { - /** @var string */ - private $markingValue; + private string $markingValue; public function __construct(string $markingValue) { diff --git a/Tests/Metadata/InMemoryMetadataStoreTest.php b/Tests/Metadata/InMemoryMetadataStoreTest.php index e2f6d05..8177579 100644 --- a/Tests/Metadata/InMemoryMetadataStoreTest.php +++ b/Tests/Metadata/InMemoryMetadataStoreTest.php @@ -20,8 +20,8 @@ */ class InMemoryMetadataStoreTest extends TestCase { - private $store; - private $transition; + private InMemoryMetadataStore $store; + private Transition $transition; protected function setUp(): void { diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index eb9e83b..f9a8fe0 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -23,7 +23,7 @@ class RegistryTest extends TestCase { - private $registry; + private Registry $registry; protected function setUp(): void { @@ -34,11 +34,6 @@ protected function setUp(): void $this->registry->addWorkflow(new Workflow(new Definition([], []), $this->createMock(MarkingStoreInterface::class), $this->createMock(EventDispatcherInterface::class), 'workflow3'), $this->createWorkflowSupportStrategy(Subject2::class)); } - protected function tearDown(): void - { - $this->registry = null; - } - public function testHasWithMatch() { $this->assertTrue($this->registry->has(new Subject1())); diff --git a/Tests/Subject.php b/Tests/Subject.php index 6dd76e1..d68d430 100644 --- a/Tests/Subject.php +++ b/Tests/Subject.php @@ -13,13 +13,12 @@ final class Subject { - private $marking; - private $context; + private string|array|null $marking; + private array $context = []; public function __construct($marking = null) { $this->marking = $marking; - $this->context = []; } public function getMarking(): string|array|null diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 2b3d1ec..f7cb3c7 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -780,7 +780,7 @@ public function testGetEnabledTransitionsWithSameNameTransition() class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface { - public $dispatchedEvents = []; + public array $dispatchedEvents = []; public function dispatch($event, string $eventName = null): object { From abae331bb94a0621683d8cee2668829166ecff24 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:36:26 +0200 Subject: [PATCH 088/145] Add types to public and protected properties --- Dumper/GraphvizDumper.php | 2 +- Event/Event.php | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 0521a14..6a36ad8 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -27,7 +27,7 @@ class GraphvizDumper implements DumperInterface { // All values should be strings - protected static $defaultOptions = [ + protected static array $defaultOptions = [ 'graph' => ['ratio' => 'compress', 'rankdir' => 'LR'], 'node' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => '#333333', 'fillcolor' => 'lightblue', 'fixedsize' => 'false', 'width' => '1'], 'edge' => ['fontsize' => '9', 'fontname' => 'Arial', 'color' => '#333333', 'arrowhead' => 'normal', 'arrowsize' => '0.5'], diff --git a/Event/Event.php b/Event/Event.php index 524c4f1..7bbdad2 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -23,7 +23,8 @@ */ class Event extends BaseEvent { - protected $context; + protected array $context; + private object $subject; private Marking $marking; private ?Transition $transition; From 83134d6d0f2a1ecfe14737682f431eccdf2ba955 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 28 Jul 2023 16:44:35 +0200 Subject: [PATCH 089/145] Fix symfony/deprecation-contracts require --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 412c4e6..32e07af 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ ], "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-php80": "^1.15" }, "require-dev": { From d7da3eb8c6d5250ddd6d7071eb45404fc3a81055 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 28 Jul 2023 17:01:18 +0200 Subject: [PATCH 090/145] Fix symfony/deprecation-contracts require --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 6b23eeb..350b022 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,8 @@ } ], "require": { - "php": ">=8.1" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "psr/log": "^1|^2|^3", From 95d880bf7c5e60f28c699256bb4ca5072a1d0eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 25 Jul 2023 14:54:15 +0200 Subject: [PATCH 091/145] [Workflow] Add support for storing the marking in a property --- CHANGELOG.md | 1 + MarkingStore/MethodMarkingStore.php | 93 ++++++++++----- Tests/MarkingStore/MethodMarkingStoreTest.php | 6 +- .../PropertiesMarkingStoreTest.php | 109 ++++++++++++++++++ Tests/MarkingStore/SubjectWithProperties.php | 26 +++++ 5 files changed, 206 insertions(+), 29 deletions(-) create mode 100644 Tests/MarkingStore/PropertiesMarkingStoreTest.php create mode 100644 Tests/MarkingStore/SubjectWithProperties.php diff --git a/CHANGELOG.md b/CHANGELOG.md index ff71628..1fc1373 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add `with-metadata` option to the `workflow:dump` command to include places, transitions and workflow's metadata into dumped graph + * Add support for storing marking in a property 6.2 --- diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index 78d3307..773328f 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -15,45 +15,43 @@ use Symfony\Component\Workflow\Marking; /** - * MethodMarkingStore stores the marking with a subject's method. + * MethodMarkingStore stores the marking with a subject's public method + * or public property. * - * This store deals with a "single state" or "multiple state" Marking. + * This store deals with a "single state" or "multiple state" marking. * - * "single state" Marking means a subject can be in one and only one state at - * the same time. Use it with state machine. + * "single state" marking means a subject can be in one and only one state at + * the same time. Use it with state machine. It uses a string to store the + * marking. * - * "multiple state" Marking means a subject can be in many states at the same - * time. Use it with workflow. + * "multiple state" marking means a subject can be in many states at the same + * time. Use it with workflow. It uses an array of strings to store the marking. * * @author Grégoire Pineau */ final class MethodMarkingStore implements MarkingStoreInterface { - private bool $singleState; - private string $property; + /** @var array */ + private array $getters = []; + /** @var array */ + private array $setters = []; /** - * @param string $property Used to determine methods to call - * The `getMarking` method will use `$subject->getProperty()` - * The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = array())` + * @param string $property Used to determine methods or property to call + * The `getMarking` method will use `$subject->getProperty()` or `$subject->property` + * The `setMarking` method will use `$subject->setProperty(string|array $places, array $context = [])` or `$subject->property = string|array $places` */ - public function __construct(bool $singleState = false, string $property = 'marking') - { - $this->singleState = $singleState; - $this->property = $property; + public function __construct( + private bool $singleState = false, + private string $property = 'marking', + ) { } public function getMarking(object $subject): Marking { - $method = 'get'.ucfirst($this->property); - - if (!method_exists($subject, $method)) { - throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method)); - } - $marking = null; try { - $marking = $subject->{$method}(); + $marking = ($this->getGetter($subject))(); } catch (\Error $e) { $unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); if ($e->getMessage() !== $unInitializedPropertyMessage) { @@ -68,7 +66,7 @@ public function getMarking(object $subject): Marking if ($this->singleState) { $marking = [(string) $marking => 1]; } elseif (!\is_array($marking)) { - throw new LogicException(sprintf('The method "%s::%s()" did not return an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $method)); + throw new LogicException(sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property)); } return new Marking($marking); @@ -82,12 +80,53 @@ public function setMarking(object $subject, Marking $marking, array $context = [ $marking = key($marking); } - $method = 'set'.ucfirst($this->property); + ($this->getSetter($subject))($marking, $context); + } + + private function getGetter(object $subject): callable + { + $property = $this->property; + $method = 'get'.ucfirst($property); + + return match ($this->getters[$subject::class] ??= $this->getType($subject, $property, $method)) { + MarkingStoreMethod::METHOD => $subject->{$method}(...), + MarkingStoreMethod::PROPERTY => static fn () => $subject->{$property}, + }; + } - if (!method_exists($subject, $method)) { - throw new LogicException(sprintf('The method "%s::%s()" does not exist.', get_debug_type($subject), $method)); + private function getSetter(object $subject): callable + { + $property = $this->property; + $method = 'set'.ucfirst($property); + + return match ($this->setters[$subject::class] ??= $this->getType($subject, $property, $method)) { + MarkingStoreMethod::METHOD => $subject->{$method}(...), + MarkingStoreMethod::PROPERTY => static fn ($marking) => $subject->{$property} = $marking, + }; + } + + private static function getType(object $subject, string $property, string $method): MarkingStoreMethod + { + if (method_exists($subject, $method) && (new \ReflectionMethod($subject, $method))->isPublic()) { + return MarkingStoreMethod::METHOD; + } + + try { + if ((new \ReflectionProperty($subject, $property))->isPublic()) { + return MarkingStoreMethod::PROPERTY; + } + } catch (\ReflectionException) { } - $subject->{$method}($marking, $context); + throw new LogicException(sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property)); } } + +/** + * @internal + */ +enum MarkingStoreMethod +{ + case METHOD; + case PROPERTY; +} diff --git a/Tests/MarkingStore/MethodMarkingStoreTest.php b/Tests/MarkingStore/MethodMarkingStoreTest.php index 1efe406..af0be68 100644 --- a/Tests/MarkingStore/MethodMarkingStoreTest.php +++ b/Tests/MarkingStore/MethodMarkingStoreTest.php @@ -29,9 +29,10 @@ public function testGetSetMarkingWithMultipleState() $marking->mark('first_place'); - $markingStore->setMarking($subject, $marking); + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); $this->assertSame(['first_place' => 1], $subject->getMarking()); + $this->assertSame(['foo' => 'bar'], $subject->getContext()); $marking2 = $markingStore->getMarking($subject); @@ -50,11 +51,12 @@ public function testGetSetMarkingWithSingleState() $marking->mark('first_place'); - $markingStore->setMarking($subject, $marking); + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); $this->assertSame('first_place', $subject->getMarking()); $marking2 = $markingStore->getMarking($subject); + $this->assertSame(['foo' => 'bar'], $subject->getContext()); $this->assertEquals($marking, $marking2); } diff --git a/Tests/MarkingStore/PropertiesMarkingStoreTest.php b/Tests/MarkingStore/PropertiesMarkingStoreTest.php new file mode 100644 index 0000000..10548e5 --- /dev/null +++ b/Tests/MarkingStore/PropertiesMarkingStoreTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\MarkingStore\MethodMarkingStore; + +class PropertiesMarkingStoreTest extends TestCase +{ + public function testGetSetMarkingWithMultipleState() + { + $subject = new SubjectWithProperties(); + $markingStore = new MethodMarkingStore(false); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + + $marking->mark('first_place'); + + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); + + $this->assertSame(['first_place' => 1], $subject->marking); + + $marking2 = $markingStore->getMarking($subject); + + $this->assertEquals($marking, $marking2); + } + + public function testGetSetMarkingWithSingleState() + { + $subject = new SubjectWithProperties(); + $markingStore = new MethodMarkingStore(true, 'place', 'placeContext'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + + $marking->mark('first_place'); + + $markingStore->setMarking($subject, $marking, ['foo' => 'bar']); + + $this->assertSame('first_place', $subject->place); + + $marking2 = $markingStore->getMarking($subject); + + $this->assertEquals($marking, $marking2); + } + + public function testGetSetMarkingWithSingleStateAndAlmostEmptyPlaceName() + { + $subject = new SubjectWithProperties(); + $subject->place = 0; + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(1, $marking->getPlaces()); + } + + public function testGetMarkingWithValueObject() + { + $subject = new SubjectWithProperties(); + $subject->place = $this->createValueObject('first_place'); + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(1, $marking->getPlaces()); + $this->assertSame('first_place', (string) $subject->place); + } + + public function testGetMarkingWithUninitializedProperty() + { + $subject = new SubjectWithProperties(); + + $markingStore = new MethodMarkingStore(true, 'place'); + + $marking = $markingStore->getMarking($subject); + + $this->assertCount(0, $marking->getPlaces()); + } + + private function createValueObject(string $markingValue): object + { + return new class($markingValue) { + public function __construct( + private string $markingValue, + ) { + } + + public function __toString(): string + { + return $this->markingValue; + } + }; + } +} diff --git a/Tests/MarkingStore/SubjectWithProperties.php b/Tests/MarkingStore/SubjectWithProperties.php new file mode 100644 index 0000000..7759448 --- /dev/null +++ b/Tests/MarkingStore/SubjectWithProperties.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\MarkingStore; + +final class SubjectWithProperties +{ + // for type=workflow + public array $marking; + + // for type=state_machine + public string $place; + + private function getMarking(): array + { + return $this->marking; + } +} From 824480ed211164a31fc42e6cea912273699a52a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 10:27:53 +0200 Subject: [PATCH 092/145] [Workflow] fix MermaidDumper when place contains special char --- Dumper/MermaidDumper.php | 8 +- Tests/Dumper/MermaidDumperTest.php | 247 ++++++++++++++--------------- 2 files changed, 126 insertions(+), 129 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index b7f5eae..9f6a5b5 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -77,7 +77,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $meta = $definition->getMetadataStore(); foreach ($definition->getPlaces() as $place) { - [$placeNode, $placeStyle] = $this->preparePlace( + [$placeNodeName, $placeNode, $placeStyle] = $this->preparePlace( $placeId, $place, $meta->getPlaceMetadata($place), @@ -91,7 +91,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $output[] = $placeStyle; } - $placeNameMap[$place] = $place.$placeId; + $placeNameMap[$place] = $placeNodeName; ++$placeId; } @@ -161,13 +161,13 @@ private function preparePlace(int $placeId, string $placeName, array $meta, bool $labelShape = '([%s])'; } - $placeNodeName = $placeName.$placeId; + $placeNodeName = 'place'.$placeId; $placeNodeFormat = '%s'.$labelShape; $placeNode = sprintf($placeNodeFormat, $placeNodeName, $placeLabel); $placeStyle = $this->styleNode($meta, $placeNodeName, $hasMarking); - return [$placeNode, $placeStyle]; + return [$placeNodeName, $placeNode, $placeStyle]; } private function styleNode(array $meta, string $nodeName, bool $hasMarking = false): string diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php index 93c1e33..5a657ed 100644 --- a/Tests/Dumper/MermaidDumperTest.php +++ b/Tests/Dumper/MermaidDumperTest.php @@ -48,9 +48,9 @@ public function testDumpWithReservedWordsAsPlacenames(Definition $definition, st } /** - * @dataProvider provideStatemachine + * @dataProvider provideStateMachine */ - public function testDumpAsStatemachine(Definition $definition, string $expected) + public function testDumpAsStateMachine(Definition $definition, string $expected) { $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_STATEMACHINE); @@ -71,82 +71,82 @@ public function testDumpWorkflowWithMarking(Definition $definition, Marking $mar $this->assertEquals($expected, $dump); } - public static function provideWorkflowDefinitionWithoutMarking(): array + public static function provideWorkflowDefinitionWithoutMarking(): iterable { - return [ - [ - self::createComplexWorkflowDefinition(), - "graph LR\n" - ."a0([\"a\"])\n" - ."b1((\"b\"))\n" - ."c2((\"c\"))\n" - ."d3((\"d\"))\n" - ."e4((\"e\"))\n" - ."f5((\"f\"))\n" - ."g6((\"g\"))\n" - ."transition0[\"t1\"]\n" - ."a0-->transition0\n" - ."transition0-->b1\n" - ."transition0-->c2\n" - ."transition1[\"t2\"]\n" - ."b1-->transition1\n" - ."transition1-->d3\n" - ."c2-->transition1\n" - ."transition2[\"My custom transition label 1\"]\n" - ."d3-->transition2\n" - ."linkStyle 6 stroke:Red\n" - ."transition2-->e4\n" - ."linkStyle 7 stroke:Red\n" - ."transition3[\"t4\"]\n" - ."d3-->transition3\n" - ."transition3-->f5\n" - ."transition4[\"t5\"]\n" - ."e4-->transition4\n" - ."transition4-->g6\n" - ."transition5[\"t6\"]\n" - ."f5-->transition5\n" - .'transition5-->g6', - ], - [ - self::createWorkflowWithSameNameTransition(), - "graph LR\n" - ."a0([\"a\"])\n" - ."b1((\"b\"))\n" - ."c2((\"c\"))\n" - ."transition0[\"a_to_bc\"]\n" - ."a0-->transition0\n" - ."transition0-->b1\n" - ."transition0-->c2\n" - ."transition1[\"b_to_c\"]\n" - ."b1-->transition1\n" - ."transition1-->c2\n" - ."transition2[\"to_a\"]\n" - ."b1-->transition2\n" - ."transition2-->a0\n" - ."transition3[\"to_a\"]\n" - ."c2-->transition3\n" - .'transition3-->a0', - ], - [ - self::createSimpleWorkflowDefinition(), - "graph LR\n" - ."a0([\"a\"])\n" - ."b1((\"b\"))\n" - ."c2((\"c\"))\n" - ."style c2 fill:DeepSkyBlue\n" - ."transition0[\"My custom transition label 2\"]\n" - ."a0-->transition0\n" - ."linkStyle 0 stroke:Grey\n" - ."transition0-->b1\n" - ."linkStyle 1 stroke:Grey\n" - ."transition1[\"t2\"]\n" - ."b1-->transition1\n" - .'transition1-->c2', - ], + yield [ + self::createComplexWorkflowDefinition(), + "graph LR\n" + ."place0([\"a\"])\n" + ."place1((\"b\"))\n" + ."place2((\"c\"))\n" + ."place3((\"d\"))\n" + ."place4((\"e\"))\n" + ."place5((\"f\"))\n" + ."place6((\"g\"))\n" + ."transition0[\"t1\"]\n" + ."place0-->transition0\n" + ."transition0-->place1\n" + ."transition0-->place2\n" + ."transition1[\"t2\"]\n" + ."place1-->transition1\n" + ."transition1-->place3\n" + ."place2-->transition1\n" + ."transition2[\"My custom transition label 1\"]\n" + ."place3-->transition2\n" + ."linkStyle 6 stroke:Red\n" + ."transition2-->place4\n" + ."linkStyle 7 stroke:Red\n" + ."transition3[\"t4\"]\n" + ."place3-->transition3\n" + ."transition3-->place5\n" + ."transition4[\"t5\"]\n" + ."place4-->transition4\n" + ."transition4-->place6\n" + ."transition5[\"t6\"]\n" + ."place5-->transition5\n" + ."transition5-->place6" + + ]; + yield [ + self::createWorkflowWithSameNameTransition(), + "graph LR\n" + ."place0([\"a\"])\n" + ."place1((\"b\"))\n" + ."place2((\"c\"))\n" + ."transition0[\"a_to_bc\"]\n" + ."place0-->transition0\n" + ."transition0-->place1\n" + ."transition0-->place2\n" + ."transition1[\"b_to_c\"]\n" + ."place1-->transition1\n" + ."transition1-->place2\n" + ."transition2[\"to_a\"]\n" + ."place1-->transition2\n" + ."transition2-->place0\n" + ."transition3[\"to_a\"]\n" + ."place2-->transition3\n" + ."transition3-->place0" + + ]; + yield [ + self::createSimpleWorkflowDefinition(), + "graph LR\n" + ."place0([\"a\"])\n" + ."place1((\"b\"))\n" + ."place2((\"c\"))\n" + ."style place2 fill:DeepSkyBlue\n" + ."transition0[\"My custom transition label 2\"]\n" + ."place0-->transition0\n" + ."linkStyle 0 stroke:Grey\n" + ."transition0-->place1\n" + ."linkStyle 1 stroke:Grey\n" + ."transition1[\"t2\"]\n" + ."place1-->transition1\n" + ."transition1-->place2" ]; } - public static function provideWorkflowWithReservedWords() + public static function provideWorkflowWithReservedWords(): iterable { $builder = new DefinitionBuilder(); @@ -158,69 +158,66 @@ public static function provideWorkflowWithReservedWords() $definition = $builder->build(); - return [ - [ - $definition, - "graph LR\n" - ."start0([\"start\"])\n" - ."subgraph1((\"subgraph\"))\n" - ."end2((\"end\"))\n" - ."finis3((\"finis\"))\n" - ."transition0[\"t0\"]\n" - ."start0-->transition0\n" - ."transition0-->end2\n" - ."subgraph1-->transition0\n" - ."transition1[\"t1\"]\n" - ."end2-->transition1\n" - .'transition1-->finis3', - ], + yield [ + $definition, + "graph LR\n" + ."place0([\"start\"])\n" + ."place1((\"subgraph\"))\n" + ."place2((\"end\"))\n" + ."place3((\"finis\"))\n" + ."transition0[\"t0\"]\n" + ."place0-->transition0\n" + ."transition0-->place2\n" + ."place1-->transition0\n" + ."transition1[\"t1\"]\n" + ."place2-->transition1\n" + ."transition1-->place3" + ]; } - public static function provideStatemachine(): array + public static function provideStateMachine(): iterable { - return [ - [ - self::createComplexStateMachineDefinition(), - "graph LR\n" - ."a0([\"a\"])\n" - ."b1((\"b\"))\n" - ."c2((\"c\"))\n" - ."d3((\"d\"))\n" - ."a0-->|\"t1\"|b1\n" - ."d3-->|\"My custom transition label 3\"|b1\n" - ."linkStyle 1 stroke:Grey\n" - ."b1-->|\"t2\"|c2\n" - .'b1-->|"t3"|d3', - ], + yield [ + self::createComplexStateMachineDefinition(), + "graph LR\n" + ."place0([\"a\"])\n" + ."place1((\"b\"))\n" + ."place2((\"c\"))\n" + ."place3((\"d\"))\n" + ."place0-->|\"t1\"|place1\n" + ."place3-->|\"My custom transition label 3\"|place1\n" + ."linkStyle 1 stroke:Grey\n" + ."place1-->|\"t2\"|place2\n" + ."place1-->|\"t3\"|place3" + ]; } - public static function provideWorkflowWithMarking(): array + public static function provideWorkflowWithMarking(): iterable { $marking = new Marking(); $marking->mark('b'); $marking->mark('c'); - return [ - [ - self::createSimpleWorkflowDefinition(), - $marking, - "graph LR\n" - ."a0([\"a\"])\n" - ."b1((\"b\"))\n" - ."style b1 stroke-width:4px\n" - ."c2((\"c\"))\n" - ."style c2 fill:DeepSkyBlue,stroke-width:4px\n" - ."transition0[\"My custom transition label 2\"]\n" - ."a0-->transition0\n" - ."linkStyle 0 stroke:Grey\n" - ."transition0-->b1\n" - ."linkStyle 1 stroke:Grey\n" - ."transition1[\"t2\"]\n" - ."b1-->transition1\n" - .'transition1-->c2', - ], + yield [ + self::createSimpleWorkflowDefinition(), + $marking, + "graph LR\n" + ."place0([\"a\"])\n" + ."place1((\"b\"))\n" + ."style place1 stroke-width:4px\n" + ."place2((\"c\"))\n" + ."style place2 fill:DeepSkyBlue,stroke-width:4px\n" + ."transition0[\"My custom transition label 2\"]\n" + ."place0-->transition0\n" + ."linkStyle 0 stroke:Grey\n" + ."transition0-->place1\n" + ."linkStyle 1 stroke:Grey\n" + ."transition1[\"t2\"]\n" + ."place1-->transition1\n" + ."transition1-->place2" + ]; } } From 20358e180892594f469ebe805b92e1c34dc0ce63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 14:56:42 +0200 Subject: [PATCH 093/145] [Workflow] Add a profiler --- CHANGELOG.md | 1 + DataCollector/WorkflowDataCollector.php | 60 +++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 DataCollector/WorkflowDataCollector.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fc1373..e1656d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add `with-metadata` option to the `workflow:dump` command to include places, transitions and workflow's metadata into dumped graph * Add support for storing marking in a property + * Add a profiler 6.2 --- diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php new file mode 100644 index 0000000..a708b26 --- /dev/null +++ b/DataCollector/WorkflowDataCollector.php @@ -0,0 +1,60 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\DataCollector\DataCollector; +use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\StateMachine; + +/** + * @author Grégoire Pineau + */ +final class WorkflowDataCollector extends DataCollector implements LateDataCollectorInterface +{ + public function __construct( + private readonly iterable $workflows, + ) { + } + + public function collect(Request $request, Response $response, \Throwable $exception = null): void + { + } + + public function lateCollect(): void + { + foreach ($this->workflows as $workflow) { + $type = $workflow instanceof StateMachine ? MermaidDumper::TRANSITION_TYPE_STATEMACHINE : MermaidDumper::TRANSITION_TYPE_WORKFLOW; + $dumper = new MermaidDumper($type); + $this->data['workflows'][$workflow->getName()] = [ + 'dump' => $dumper->dump($workflow->getDefinition()), + ]; + } + } + + public function getName(): string + { + return 'workflow'; + } + + public function reset(): void + { + $this->data = []; + } + + public function getWorkflows(): array + { + return $this->data['workflows'] ?? []; + } +} From 1e68f1436d5657cf068aa53230212f205c349e05 Mon Sep 17 00:00:00 2001 From: valtzu Date: Tue, 1 Aug 2023 21:03:23 +0300 Subject: [PATCH 094/145] [Workflow] Support multiline descriptions in PlantUML --- CHANGELOG.md | 1 + Dumper/PlantUmlDumper.php | 4 +++- Tests/Dumper/PlantUmlDumperTest.php | 2 +- Tests/fixtures/puml/square/simple-workflow-with-spaces.puml | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e1656d6..117b124 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ CHANGELOG transitions and workflow's metadata into dumped graph * Add support for storing marking in a property * Add a profiler + * Add support for multiline descriptions in PlantUML diagrams 6.2 --- diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 52ff98d..ad8cdac 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -208,7 +208,9 @@ private function getState(string $place, Definition $definition, Marking $markin $description = $workflowMetadata->getMetadata('description', $place); if (null !== $description) { - $output .= \PHP_EOL.$placeEscaped.' : '.str_replace("\n", ' ', $description); + foreach (array_filter(explode("\n", $description)) as $line) { + $output .= "\n".$placeEscaped.' : '.$line; + } } return $output; diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 6e1b303..71e4065 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -83,7 +83,7 @@ public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() $placesMetadata = []; $placesMetadata['place a'] = [ - 'description' => 'My custom place description', + 'description' => "My custom\nplace description", ]; $inMemoryMetadataStore = new InMemoryMetadataStore([], $placesMetadata); $definition = new Definition($places, $transitions, null, $inMemoryMetadataStore); diff --git a/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml b/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml index 0e20d27..a62e8e0 100644 --- a/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml +++ b/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml @@ -15,7 +15,8 @@ skinparam agent { BorderColor #3887C6 } state "place a" <> -"place a" : My custom place description +"place a" : My custom +"place a" : place description state "place b" agent "t 1" "place a" --> "t 1" From d609dc7be5f4fb6c4f98863527e88686e14b2cc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 17:38:14 +0200 Subject: [PATCH 095/145] [Workflow] Add some PHP attributes to register listeners and guards --- Attribute/AsAnnounceListener.php | 33 ++++++++++ Attribute/AsCompletedListener.php | 33 ++++++++++ Attribute/AsEnterListener.php | 33 ++++++++++ Attribute/AsEnteredListener.php | 33 ++++++++++ Attribute/AsGuardListener.php | 33 ++++++++++ Attribute/AsLeaveListener.php | 33 ++++++++++ Attribute/AsTransitionListener.php | 33 ++++++++++ Attribute/BuildEventNameTrait.php | 39 ++++++++++++ CHANGELOG.md | 1 + Tests/Attribute/AsListenerTest.php | 97 ++++++++++++++++++++++++++++++ 10 files changed, 368 insertions(+) create mode 100644 Attribute/AsAnnounceListener.php create mode 100644 Attribute/AsCompletedListener.php create mode 100644 Attribute/AsEnterListener.php create mode 100644 Attribute/AsEnteredListener.php create mode 100644 Attribute/AsGuardListener.php create mode 100644 Attribute/AsLeaveListener.php create mode 100644 Attribute/AsTransitionListener.php create mode 100644 Attribute/BuildEventNameTrait.php create mode 100644 Tests/Attribute/AsListenerTest.php diff --git a/Attribute/AsAnnounceListener.php b/Attribute/AsAnnounceListener.php new file mode 100644 index 0000000..01669dc --- /dev/null +++ b/Attribute/AsAnnounceListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsAnnounceListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $transition = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('announce', 'transition', $workflow, $transition), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsCompletedListener.php b/Attribute/AsCompletedListener.php new file mode 100644 index 0000000..012b304 --- /dev/null +++ b/Attribute/AsCompletedListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsCompletedListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $transition = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('completed', 'transition', $workflow, $transition), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsEnterListener.php b/Attribute/AsEnterListener.php new file mode 100644 index 0000000..fe55f6e --- /dev/null +++ b/Attribute/AsEnterListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsEnterListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $place = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('enter', 'place', $workflow, $place), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsEnteredListener.php b/Attribute/AsEnteredListener.php new file mode 100644 index 0000000..474cf09 --- /dev/null +++ b/Attribute/AsEnteredListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsEnteredListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $place = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('entered', 'place', $workflow, $place), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsGuardListener.php b/Attribute/AsGuardListener.php new file mode 100644 index 0000000..994fe32 --- /dev/null +++ b/Attribute/AsGuardListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsGuardListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $transition = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('guard', 'transition', $workflow, $transition), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsLeaveListener.php b/Attribute/AsLeaveListener.php new file mode 100644 index 0000000..e4ea4dc --- /dev/null +++ b/Attribute/AsLeaveListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsLeaveListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $place = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('leave', 'place', $workflow, $place), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/AsTransitionListener.php b/Attribute/AsTransitionListener.php new file mode 100644 index 0000000..589ef7a --- /dev/null +++ b/Attribute/AsTransitionListener.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\EventDispatcher\Attribute\AsEventListener; + +/** + * @author Grégoire Pineau + */ +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] +final class AsTransitionListener extends AsEventListener +{ + use BuildEventNameTrait; + + public function __construct( + string $workflow = null, + string $transition = null, + string $method = null, + int $priority = 0, + string $dispatcher = null, + ) { + parent::__construct($this->buildEventName('transition', 'transition', $workflow, $transition), $method, $priority, $dispatcher); + } +} diff --git a/Attribute/BuildEventNameTrait.php b/Attribute/BuildEventNameTrait.php new file mode 100644 index 0000000..0ca7a09 --- /dev/null +++ b/Attribute/BuildEventNameTrait.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Attribute; + +use Symfony\Component\Workflow\Exception\LogicException; + +/** + * @author Grégoire Pineau + * + * @internal + */ +trait BuildEventNameTrait +{ + private static function buildEventName(string $keyword, string $argument, string $workflow = null, string $node = null): string + { + if (null === $workflow) { + if (null !== $node) { + throw new LogicException(sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class)); + } + + return sprintf('workflow.%s', $keyword); + } + + if (null === $node) { + return sprintf('workflow.%s.%s', $workflow, $keyword); + } + + return sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node); + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 117b124..ecc900e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ CHANGELOG * Add support for storing marking in a property * Add a profiler * Add support for multiline descriptions in PlantUML diagrams + * Add PHP attributes to register listeners and guards 6.2 --- diff --git a/Tests/Attribute/AsListenerTest.php b/Tests/Attribute/AsListenerTest.php new file mode 100644 index 0000000..78de4e0 --- /dev/null +++ b/Tests/Attribute/AsListenerTest.php @@ -0,0 +1,97 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\Attribute; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Attribute; +use Symfony\Component\Workflow\Exception\LogicException; + +class AsListenerTest extends TestCase +{ + /** + * @dataProvider provideOkTests + */ + public function testOk(string $class, string $expectedEvent, string $workflow = null, string $node = null) + { + $attribute = new $class($workflow, $node); + + $this->assertSame($expectedEvent, $attribute->event); + } + + public static function provideOkTests(): iterable + { + yield [Attribute\AsAnnounceListener::class, 'workflow.announce']; + yield [Attribute\AsAnnounceListener::class, 'workflow.w.announce', 'w']; + yield [Attribute\AsAnnounceListener::class, 'workflow.w.announce.n', 'w', 'n']; + + yield [Attribute\AsCompletedListener::class, 'workflow.completed']; + yield [Attribute\AsCompletedListener::class, 'workflow.w.completed', 'w']; + yield [Attribute\AsCompletedListener::class, 'workflow.w.completed.n', 'w', 'n']; + + yield [Attribute\AsEnterListener::class, 'workflow.enter']; + yield [Attribute\AsEnterListener::class, 'workflow.w.enter', 'w']; + yield [Attribute\AsEnterListener::class, 'workflow.w.enter.n', 'w', 'n']; + + yield [Attribute\AsEnteredListener::class, 'workflow.entered']; + yield [Attribute\AsEnteredListener::class, 'workflow.w.entered', 'w']; + yield [Attribute\AsEnteredListener::class, 'workflow.w.entered.n', 'w', 'n']; + + yield [Attribute\AsGuardListener::class, 'workflow.guard']; + yield [Attribute\AsGuardListener::class, 'workflow.w.guard', 'w']; + yield [Attribute\AsGuardListener::class, 'workflow.w.guard.n', 'w', 'n']; + + yield [Attribute\AsLeaveListener::class, 'workflow.leave']; + yield [Attribute\AsLeaveListener::class, 'workflow.w.leave', 'w']; + yield [Attribute\AsLeaveListener::class, 'workflow.w.leave.n', 'w', 'n']; + + yield [Attribute\AsTransitionListener::class, 'workflow.transition']; + yield [Attribute\AsTransitionListener::class, 'workflow.w.transition', 'w']; + yield [Attribute\AsTransitionListener::class, 'workflow.w.transition.n', 'w', 'n']; + } + + /** + * @dataProvider provideTransitionThrowException + */ + public function testTransitionThrowException(string $class) + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage(sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class)); + + new $class(transition: 'some'); + } + + public static function provideTransitionThrowException(): iterable + { + yield [Attribute\AsAnnounceListener::class, 'workflow.announce']; + yield [Attribute\AsCompletedListener::class, 'workflow.completed']; + yield [Attribute\AsGuardListener::class, 'workflow.guard']; + yield [Attribute\AsTransitionListener::class, 'workflow.transition']; + } + + /** + * @dataProvider providePlaceThrowException + */ + public function testPlaceThrowException(string $class) + { + $this->expectException(LogicException::class); + $this->expectExceptionMessage(sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class)); + + new $class(place: 'some'); + } + + public static function providePlaceThrowException(): iterable + { + yield [Attribute\AsEnteredListener::class, 'workflow.entered']; + yield [Attribute\AsEnterListener::class, 'workflow.enter']; + yield [Attribute\AsLeaveListener::class, 'workflow.leave']; + } +} From e928cb27a5b6a9f83b1852098417d254275fd7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 19:17:13 +0200 Subject: [PATCH 096/145] [Workflow] Use TRANSITION_TYPE_WORKFLOW for rendering workflow in profiler --- DataCollector/WorkflowDataCollector.php | 6 +++--- Dumper/MermaidDumper.php | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index a708b26..2839d31 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -16,7 +16,6 @@ use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; use Symfony\Component\Workflow\Dumper\MermaidDumper; -use Symfony\Component\Workflow\StateMachine; /** * @author Grégoire Pineau @@ -35,8 +34,9 @@ public function collect(Request $request, Response $response, \Throwable $except public function lateCollect(): void { foreach ($this->workflows as $workflow) { - $type = $workflow instanceof StateMachine ? MermaidDumper::TRANSITION_TYPE_STATEMACHINE : MermaidDumper::TRANSITION_TYPE_WORKFLOW; - $dumper = new MermaidDumper($type); + // We always use a workflow type because we want to mermaid to + // create a node for transitions + $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW); $this->data['workflows'][$workflow->getName()] = [ 'dump' => $dumper->dump($workflow->getDefinition()), ]; diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 25da583..2d0f958 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -102,7 +102,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $to = $placeNameMap[$to]; if (self::TRANSITION_TYPE_STATEMACHINE === $this->transitionType) { - $transitionOutput = $this->styleStatemachineTransition($from, $to, $transitionLabel, $transitionMeta); + $transitionOutput = $this->styleStateMachineTransition($from, $to, $transitionLabel, $transitionMeta); } else { $transitionOutput = $this->styleWorkflowTransition($from, $to, $transitionId, $transitionLabel, $transitionMeta); } @@ -196,7 +196,7 @@ private function validateTransitionType(string $transitionType): void } } - private function styleStatemachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array + private function styleStateMachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array { $transitionOutput = [sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; From 9926ee0a119b7dbcda63caf319a82cabcaba244a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 25 Aug 2023 18:21:02 +0200 Subject: [PATCH 097/145] =?UTF-8?q?Update=20the=20list=20of=20sponsors=20f?= =?UTF-8?q?or=20Symfony=206.4=20=F0=9F=99=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 822a29d..7813d63 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,7 @@ machine. Sponsor ------- -The Workflow component for Symfony 6.2 is [backed][1] by [bitExpert][2]. - -Their pulse is cross-technology software development that beats with every line of code. -The basic principle for their solutions, products, and services is innovation, quality, -commitment, and professionalism. - -bitExpert actively supports Open-Source and the software development community through various -activities: Contributing to the Open-Source projects they love, organizing & hosting meetups, -speaking at conferences, and organizing unKonf - an unconference focused on web -and software development practices. - -Help Symfony by [sponsoring][3] its development! +Help Symfony by [sponsoring][1] its development! Resources --------- @@ -29,6 +18,4 @@ Resources [send Pull Requests](https://github.com/symfony/symfony/pulls) in the [main Symfony repository](https://github.com/symfony/symfony) -[1]: https://symfony.com/backers -[2]: https://www.bitexpert.de -[3]: https://symfony.com/sponsor +[1]: https://symfony.com/sponsor From ec1531d5cfad7114b25f72fe038f61dd953dc57a Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Fri, 25 Aug 2023 14:40:50 +0200 Subject: [PATCH 098/145] [Workflow] deprecate `GuardEvent::getContext` method --- CHANGELOG.md | 1 + Event/GuardEvent.php | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ecc900e..5089019 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ CHANGELOG * Add a profiler * Add support for multiline descriptions in PlantUML diagrams * Add PHP attributes to register listeners and guards + * Deprecate `GuardEvent::getContext()` method that will be removed in 7.0 6.2 --- diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index 9409da2..fe8ba35 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -32,6 +32,13 @@ public function __construct(object $subject, Marking $marking, Transition $trans $this->transitionBlockerList = new TransitionBlockerList(); } + public function getContext(): array + { + trigger_deprecation('symfony/workflow', '6.4', 'The %s::getContext() method is deprecated and will be removed in 7.0. You should no longer call this method as it always returns an empty array when invoked within a guard listener.', __CLASS__); + + return parent::getContext(); + } + public function getTransition(): Transition { return parent::getTransition(); From 83a806cf5ba6c997192ec1a7cc9591a854705e7f Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Sat, 26 Aug 2023 13:27:34 +0200 Subject: [PATCH 099/145] Remove `GuardEvent::getContext()` method and add `HasContextTrait` trait --- CHANGELOG.md | 1 + Event/AnnounceEvent.php | 12 ++++++++++++ Event/CompletedEvent.php | 12 ++++++++++++ Event/EnterEvent.php | 12 ++++++++++++ Event/EnteredEvent.php | 12 ++++++++++++ Event/Event.php | 10 +--------- Event/HasContextTrait.php | 29 +++++++++++++++++++++++++++++ Event/LeaveEvent.php | 12 ++++++++++++ Event/TransitionEvent.php | 13 +++++++++++++ 9 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 Event/HasContextTrait.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a272070..db56894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Require explicit argument when calling `Definition::setInitialPlaces()` + * `GuardEvent::getContext()` method has been removed. Method was not supposed to be called within guard event listeners as it always returned an empty array anyway. 6.4 --- diff --git a/Event/AnnounceEvent.php b/Event/AnnounceEvent.php index 7d3d740..ff0cfe5 100644 --- a/Event/AnnounceEvent.php +++ b/Event/AnnounceEvent.php @@ -11,6 +11,18 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class AnnounceEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } } diff --git a/Event/CompletedEvent.php b/Event/CompletedEvent.php index 883390e..9643d42 100644 --- a/Event/CompletedEvent.php +++ b/Event/CompletedEvent.php @@ -11,6 +11,18 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class CompletedEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } } diff --git a/Event/EnterEvent.php b/Event/EnterEvent.php index 3296f29..3a64cfa 100644 --- a/Event/EnterEvent.php +++ b/Event/EnterEvent.php @@ -11,6 +11,18 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class EnterEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } } diff --git a/Event/EnteredEvent.php b/Event/EnteredEvent.php index ea3624b..0413242 100644 --- a/Event/EnteredEvent.php +++ b/Event/EnteredEvent.php @@ -11,6 +11,18 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class EnteredEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } } diff --git a/Event/Event.php b/Event/Event.php index 7bbdad2..66eada4 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -23,20 +23,17 @@ */ class Event extends BaseEvent { - protected array $context; - private object $subject; private Marking $marking; private ?Transition $transition; private ?WorkflowInterface $workflow; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null) { $this->subject = $subject; $this->marking = $marking; $this->transition = $transition; $this->workflow = $workflow; - $this->context = $context; } public function getMarking(): Marking @@ -68,9 +65,4 @@ public function getMetadata(string $key, string|Transition|null $subject): mixed { return $this->workflow->getMetadataStore()->getMetadata($key, $subject); } - - public function getContext(): array - { - return $this->context; - } } diff --git a/Event/HasContextTrait.php b/Event/HasContextTrait.php new file mode 100644 index 0000000..4fc3d87 --- /dev/null +++ b/Event/HasContextTrait.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Event; + +/** + * @author Fabien Potencier + * @author Grégoire Pineau + * @author Hugo Hamon + * + * @internal + */ +trait HasContextTrait +{ + private array $context = []; + + public function getContext(): array + { + return $this->context; + } +} diff --git a/Event/LeaveEvent.php b/Event/LeaveEvent.php index d3d48cb..a50d7b3 100644 --- a/Event/LeaveEvent.php +++ b/Event/LeaveEvent.php @@ -11,6 +11,18 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class LeaveEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } } diff --git a/Event/TransitionEvent.php b/Event/TransitionEvent.php index 4710f90..e9a82a0 100644 --- a/Event/TransitionEvent.php +++ b/Event/TransitionEvent.php @@ -11,8 +11,21 @@ namespace Symfony\Component\Workflow\Event; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; +use Symfony\Component\Workflow\WorkflowInterface; + final class TransitionEvent extends Event { + use HasContextTrait; + + public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + { + parent::__construct($subject, $marking, $transition, $workflow); + + $this->context = $context; + } + public function setContext(array $context): void { $this->context = $context; From 779757f63b255dfb6bbb0cb4c9bdb6ec46433283 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 4 Sep 2023 13:02:33 +0200 Subject: [PATCH 100/145] [Workflow] Remove `GuardEvent::getContext()` method without replacement --- CHANGELOG.md | 1 + Event/GuardEvent.php | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f09d7..59a15f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Require explicit argument when calling `Definition::setInitialPlaces()` * `GuardEvent::getContext()` method has been removed. Method was not supposed to be called within guard event listeners as it always returned an empty array anyway. + * Remove `GuardEvent::getContext()` method without replacement 6.4 --- diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index fe8ba35..9409da2 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -32,13 +32,6 @@ public function __construct(object $subject, Marking $marking, Transition $trans $this->transitionBlockerList = new TransitionBlockerList(); } - public function getContext(): array - { - trigger_deprecation('symfony/workflow', '6.4', 'The %s::getContext() method is deprecated and will be removed in 7.0. You should no longer call this method as it always returns an empty array when invoked within a guard listener.', __CLASS__); - - return parent::getContext(); - } - public function getTransition(): Transition { return parent::getTransition(); From a6b32190919a4d5fdc89db5e5f324af316a32e99 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 25 Sep 2023 14:52:38 +0200 Subject: [PATCH 101/145] Minor CS fixes --- Tests/Dumper/MermaidDumperTest.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php index 5a657ed..3a29da6 100644 --- a/Tests/Dumper/MermaidDumperTest.php +++ b/Tests/Dumper/MermaidDumperTest.php @@ -104,8 +104,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."transition4-->place6\n" ."transition5[\"t6\"]\n" ."place5-->transition5\n" - ."transition5-->place6" - + ."transition5-->place6", ]; yield [ self::createWorkflowWithSameNameTransition(), @@ -125,8 +124,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."transition2-->place0\n" ."transition3[\"to_a\"]\n" ."place2-->transition3\n" - ."transition3-->place0" - + ."transition3-->place0", ]; yield [ self::createSimpleWorkflowDefinition(), @@ -142,7 +140,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."linkStyle 1 stroke:Grey\n" ."transition1[\"t2\"]\n" ."place1-->transition1\n" - ."transition1-->place2" + ."transition1-->place2", ]; } @@ -171,8 +169,7 @@ public static function provideWorkflowWithReservedWords(): iterable ."place1-->transition0\n" ."transition1[\"t1\"]\n" ."place2-->transition1\n" - ."transition1-->place3" - + ."transition1-->place3", ]; } @@ -189,8 +186,7 @@ public static function provideStateMachine(): iterable ."place3-->|\"My custom transition label 3\"|place1\n" ."linkStyle 1 stroke:Grey\n" ."place1-->|\"t2\"|place2\n" - ."place1-->|\"t3\"|place3" - + ."place1-->|\"t3\"|place3", ]; } @@ -216,8 +212,7 @@ public static function provideWorkflowWithMarking(): iterable ."linkStyle 1 stroke:Grey\n" ."transition1[\"t2\"]\n" ."place1-->transition1\n" - ."transition1-->place2" - + ."transition1-->place2", ]; } } From df35f90a91c091f899384ea9e53e5112ac871d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 10 Oct 2023 17:19:56 +0200 Subject: [PATCH 102/145] [workflow] Revert deprecation about Registry --- CHANGELOG.md | 1 + Registry.php | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5089019..da2bcd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ CHANGELOG * Add support for multiline descriptions in PlantUML diagrams * Add PHP attributes to register listeners and guards * Deprecate `GuardEvent::getContext()` method that will be removed in 7.0 + * Revert: Mark `Symfony\Component\Workflow\Registry` as internal 6.2 --- diff --git a/Registry.php b/Registry.php index 287d8b7..e9d9481 100644 --- a/Registry.php +++ b/Registry.php @@ -17,8 +17,6 @@ /** * @author Fabien Potencier * @author Grégoire Pineau - * - * @internal since Symfony 6.2. Inject the workflow where you need it. */ class Registry { From 33273265ee43ac9afe45a89fcedf380de0e95159 Mon Sep 17 00:00:00 2001 From: Thomas Calvet Date: Fri, 13 Oct 2023 10:59:13 +0200 Subject: [PATCH 103/145] [FrameworkBundle][Routing][Translation][Workflow] Move some compiler passes from FrameworkBundle to components --- CHANGELOG.md | 1 + .../WorkflowGuardListenerPass.php | 45 ++++++++ .../WorkflowGuardListenerPassTest.php | 107 ++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 DependencyInjection/WorkflowGuardListenerPass.php create mode 100644 Tests/DependencyInjection/WorkflowGuardListenerPassTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index da2bcd2..009bb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Add PHP attributes to register listeners and guards * Deprecate `GuardEvent::getContext()` method that will be removed in 7.0 * Revert: Mark `Symfony\Component\Workflow\Registry` as internal + * Add `WorkflowGuardListenerPass` (moved from `FrameworkBundle`) 6.2 --- diff --git a/DependencyInjection/WorkflowGuardListenerPass.php b/DependencyInjection/WorkflowGuardListenerPass.php new file mode 100644 index 0000000..ba81a7b --- /dev/null +++ b/DependencyInjection/WorkflowGuardListenerPass.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * @author Christian Flothmann + * @author Grégoire Pineau + */ +class WorkflowGuardListenerPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + if (!$container->hasParameter('workflow.has_guard_listeners')) { + return; + } + + $container->getParameterBag()->remove('workflow.has_guard_listeners'); + + $servicesNeeded = [ + 'security.token_storage', + 'security.authorization_checker', + 'security.authentication.trust_resolver', + 'security.role_hierarchy', + ]; + + foreach ($servicesNeeded as $service) { + if (!$container->has($service)) { + throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); + } + } + } +} diff --git a/Tests/DependencyInjection/WorkflowGuardListenerPassTest.php b/Tests/DependencyInjection/WorkflowGuardListenerPassTest.php new file mode 100644 index 0000000..4e69a9c --- /dev/null +++ b/Tests/DependencyInjection/WorkflowGuardListenerPassTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Role\RoleHierarchy; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass; + +class WorkflowGuardListenerPassTest extends TestCase +{ + private ContainerBuilder $container; + private WorkflowGuardListenerPass $compilerPass; + + protected function setUp(): void + { + $this->container = new ContainerBuilder(); + $this->compilerPass = new WorkflowGuardListenerPass(); + } + + public function testNoExeptionIfParameterIsNotSet() + { + $this->compilerPass->process($this->container); + + $this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners')); + } + + public function testNoExeptionIfAllDependenciesArePresent() + { + $this->container->setParameter('workflow.has_guard_listeners', true); + $this->container->register('security.token_storage', TokenStorageInterface::class); + $this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class); + $this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class); + $this->container->register('security.role_hierarchy', RoleHierarchy::class); + $this->container->register('validator', ValidatorInterface::class); + + $this->compilerPass->process($this->container); + + $this->assertFalse($this->container->hasParameter('workflow.has_guard_listeners')); + } + + public function testExceptionIfTheTokenStorageServiceIsNotPresent() + { + $this->container->setParameter('workflow.has_guard_listeners', true); + $this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class); + $this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class); + $this->container->register('security.role_hierarchy', RoleHierarchy::class); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "security.token_storage" service is needed to be able to use the workflow guard listener.'); + + $this->compilerPass->process($this->container); + } + + public function testExceptionIfTheAuthorizationCheckerServiceIsNotPresent() + { + $this->container->setParameter('workflow.has_guard_listeners', true); + $this->container->register('security.token_storage', TokenStorageInterface::class); + $this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class); + $this->container->register('security.role_hierarchy', RoleHierarchy::class); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "security.authorization_checker" service is needed to be able to use the workflow guard listener.'); + + $this->compilerPass->process($this->container); + } + + public function testExceptionIfTheAuthenticationTrustResolverServiceIsNotPresent() + { + $this->container->setParameter('workflow.has_guard_listeners', true); + $this->container->register('security.token_storage', TokenStorageInterface::class); + $this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class); + $this->container->register('security.role_hierarchy', RoleHierarchy::class); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "security.authentication.trust_resolver" service is needed to be able to use the workflow guard listener.'); + + $this->compilerPass->process($this->container); + } + + public function testExceptionIfTheRoleHierarchyServiceIsNotPresent() + { + $this->container->setParameter('workflow.has_guard_listeners', true); + $this->container->register('security.token_storage', TokenStorageInterface::class); + $this->container->register('security.authorization_checker', AuthorizationCheckerInterface::class); + $this->container->register('security.authentication.trust_resolver', AuthenticationTrustResolverInterface::class); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('The "security.role_hierarchy" service is needed to be able to use the workflow guard listener.'); + + $this->compilerPass->process($this->container); + } +} From 47482591cb6eb22beda31b7130d3e109e93fd301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 21:15:44 +0200 Subject: [PATCH 104/145] [Worflow] Add a TraceableWorkflow --- DataCollector/WorkflowDataCollector.php | 45 ++++++++ Debug/TraceableWorkflow.php | 122 ++++++++++++++++++++++ DependencyInjection/WorkflowDebugPass.php | 37 +++++++ Registry.php | 2 +- 4 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 Debug/TraceableWorkflow.php create mode 100644 DependencyInjection/WorkflowDebugPass.php diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index 2839d31..6978012 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -15,7 +15,12 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\Workflow\Debug\TraceableWorkflow; use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\TransitionBlocker; /** * @author Grégoire Pineau @@ -34,11 +39,17 @@ public function collect(Request $request, Response $response, \Throwable $except public function lateCollect(): void { foreach ($this->workflows as $workflow) { + $calls = []; + if ($workflow instanceof TraceableWorkflow) { + $calls = $this->cloneVar($workflow->getCalls()); + } + // We always use a workflow type because we want to mermaid to // create a node for transitions $dumper = new MermaidDumper(MermaidDumper::TRANSITION_TYPE_WORKFLOW); $this->data['workflows'][$workflow->getName()] = [ 'dump' => $dumper->dump($workflow->getDefinition()), + 'calls' => $calls, ]; } } @@ -57,4 +68,38 @@ public function getWorkflows(): array { return $this->data['workflows'] ?? []; } + + public function getCallsCount(): int + { + $i = 0; + foreach ($this->getWorkflows() as $workflow) { + $i += \count($workflow['calls']); + } + + return $i; + } + + protected function getCasters(): array + { + $casters = [ + ...parent::getCasters(), + TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) { + unset( + $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], + $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], + ); + + $s->cut += 2; + + return $a; + }, + Marking::class => function ($v, array $a, Stub $s, $isNested) { + $a[Caster::PREFIX_VIRTUAL.'.places'] = array_keys($v->getPlaces()); + + return $a; + }, + ]; + + return $casters; + } } diff --git a/Debug/TraceableWorkflow.php b/Debug/TraceableWorkflow.php new file mode 100644 index 0000000..b8ac086 --- /dev/null +++ b/Debug/TraceableWorkflow.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Debug; + +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; +use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; +use Symfony\Component\Workflow\TransitionBlockerList; +use Symfony\Component\Workflow\WorkflowInterface; + +/** + * @author Grégoire Pineau + */ +class TraceableWorkflow implements WorkflowInterface +{ + private array $calls = []; + + public function __construct( + private readonly WorkflowInterface $workflow, + private readonly Stopwatch $stopwatch, + ) { + } + + public function getMarking(object $subject, array $context = []): Marking + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + + public function can(object $subject, string $transitionName): bool + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + + public function buildTransitionBlockerList(object $subject, string $transitionName): TransitionBlockerList + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + + public function apply(object $subject, string $transitionName, array $context = []): Marking + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + + public function getEnabledTransitions(object $subject): array + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + + public function getName(): string + { + return $this->workflow->getName(); + } + + public function getDefinition(): Definition + { + return $this->workflow->getDefinition(); + } + + public function getMarkingStore(): MarkingStoreInterface + { + return $this->workflow->getMarkingStore(); + } + + public function getMetadataStore(): MetadataStoreInterface + { + return $this->workflow->getMetadataStore(); + } + + public function getCalls(): array + { + return $this->calls; + } + + private function callInner(string $method, array $args): mixed + { + $sMethod = $this->workflow::class.'::'.$method; + $this->stopwatch->start($sMethod, 'workflow'); + + $previousMarking = null; + if ('apply' === $method) { + try { + $previousMarking = $this->workflow->getMarking($args[0]); + } catch (\Throwable) { + } + } + + try { + $return = $this->workflow->{$method}(...$args); + + $this->calls[] = [ + 'method' => $method, + 'duration' => $this->stopwatch->stop($sMethod)->getDuration(), + 'args' => $args, + 'previousMarking' => $previousMarking ?? null, + 'return' => $return, + ]; + + return $return; + } catch (\Throwable $exception) { + $this->calls[] = [ + 'method' => $method, + 'duration' => $this->stopwatch->stop($sMethod)->getDuration(), + 'args' => $args, + 'previousMarking' => $previousMarking ?? null, + 'exception' => $exception, + ]; + + throw $exception; + } + } +} diff --git a/DependencyInjection/WorkflowDebugPass.php b/DependencyInjection/WorkflowDebugPass.php new file mode 100644 index 0000000..634605d --- /dev/null +++ b/DependencyInjection/WorkflowDebugPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\DependencyInjection; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\Workflow\Debug\TraceableWorkflow; + +/** + * Adds all configured security voters to the access decision manager. + * + * @author Grégoire Pineau + */ +class WorkflowDebugPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds('workflow') as $id => $attributes) { + $container->register("debug.{$id}", TraceableWorkflow::class) + ->setDecoratedService($id) + ->setArguments([ + new Reference("debug.{$id}.inner"), + new Reference('debug.stopwatch'), + ]); + } + } +} diff --git a/Registry.php b/Registry.php index e9d9481..bfe25fa 100644 --- a/Registry.php +++ b/Registry.php @@ -41,7 +41,7 @@ public function has(object $subject, string $workflowName = null): bool return false; } - public function get(object $subject, string $workflowName = null): Workflow + public function get(object $subject, string $workflowName = null): WorkflowInterface { $matched = []; From c8158df3233cc02d558ec258c8cd5cd19c747a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20Andr=C3=A9?= Date: Thu, 19 Oct 2023 15:22:12 +0200 Subject: [PATCH 105/145] Rename fixtures paths to be consistent --- Tests/Dumper/PlantUmlDumperTest.php | 2 +- .../puml/arrow/complex-state-machine-marking.puml | 0 .../puml/arrow/complex-state-machine-nomarking.puml | 0 .../puml/square/complex-workflow-marking.puml | 0 .../puml/square/complex-workflow-nomarking.puml | 0 .../puml/square/simple-workflow-marking.puml | 0 .../puml/square/simple-workflow-nomarking.puml | 0 .../puml/square/simple-workflow-with-spaces.puml | 0 8 files changed, 1 insertion(+), 1 deletion(-) rename Tests/{fixtures => Fixtures}/puml/arrow/complex-state-machine-marking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/arrow/complex-state-machine-nomarking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/square/complex-workflow-marking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/square/complex-workflow-nomarking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/square/simple-workflow-marking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/square/simple-workflow-nomarking.puml (100%) rename Tests/{fixtures => Fixtures}/puml/square/simple-workflow-with-spaces.puml (100%) diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 71e4065..3b0d010 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -96,6 +96,6 @@ public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() private function getFixturePath($name, $transitionType): string { - return __DIR__.'/../fixtures/puml/'.$transitionType.'/'.$name.'.puml'; + return __DIR__ . '/../Fixtures/puml/' .$transitionType.'/'.$name.'.puml'; } } diff --git a/Tests/fixtures/puml/arrow/complex-state-machine-marking.puml b/Tests/Fixtures/puml/arrow/complex-state-machine-marking.puml similarity index 100% rename from Tests/fixtures/puml/arrow/complex-state-machine-marking.puml rename to Tests/Fixtures/puml/arrow/complex-state-machine-marking.puml diff --git a/Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml b/Tests/Fixtures/puml/arrow/complex-state-machine-nomarking.puml similarity index 100% rename from Tests/fixtures/puml/arrow/complex-state-machine-nomarking.puml rename to Tests/Fixtures/puml/arrow/complex-state-machine-nomarking.puml diff --git a/Tests/fixtures/puml/square/complex-workflow-marking.puml b/Tests/Fixtures/puml/square/complex-workflow-marking.puml similarity index 100% rename from Tests/fixtures/puml/square/complex-workflow-marking.puml rename to Tests/Fixtures/puml/square/complex-workflow-marking.puml diff --git a/Tests/fixtures/puml/square/complex-workflow-nomarking.puml b/Tests/Fixtures/puml/square/complex-workflow-nomarking.puml similarity index 100% rename from Tests/fixtures/puml/square/complex-workflow-nomarking.puml rename to Tests/Fixtures/puml/square/complex-workflow-nomarking.puml diff --git a/Tests/fixtures/puml/square/simple-workflow-marking.puml b/Tests/Fixtures/puml/square/simple-workflow-marking.puml similarity index 100% rename from Tests/fixtures/puml/square/simple-workflow-marking.puml rename to Tests/Fixtures/puml/square/simple-workflow-marking.puml diff --git a/Tests/fixtures/puml/square/simple-workflow-nomarking.puml b/Tests/Fixtures/puml/square/simple-workflow-nomarking.puml similarity index 100% rename from Tests/fixtures/puml/square/simple-workflow-nomarking.puml rename to Tests/Fixtures/puml/square/simple-workflow-nomarking.puml diff --git a/Tests/fixtures/puml/square/simple-workflow-with-spaces.puml b/Tests/Fixtures/puml/square/simple-workflow-with-spaces.puml similarity index 100% rename from Tests/fixtures/puml/square/simple-workflow-with-spaces.puml rename to Tests/Fixtures/puml/square/simple-workflow-with-spaces.puml From ac9f2622492b80ba71337664fd97db8d4b5ad599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 1 Aug 2023 15:38:59 +0200 Subject: [PATCH 106/145] [Workflow] List place or transition listeners in profiler --- DataCollector/WorkflowDataCollector.php | 125 ++++++++++++++++++ .../WorkflowDataCollectorTest.php | 92 +++++++++++++ composer.json | 2 + 3 files changed, 219 insertions(+) create mode 100644 Tests/DataCollector/WorkflowDataCollectorTest.php diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index 6978012..656594d 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Workflow\DataCollector; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\DataCollector\DataCollector; @@ -19,8 +21,12 @@ use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\Workflow\Debug\TraceableWorkflow; use Symfony\Component\Workflow\Dumper\MermaidDumper; +use Symfony\Component\Workflow\EventListener\GuardExpression; +use Symfony\Component\Workflow\EventListener\GuardListener; use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlocker; +use Symfony\Component\Workflow\WorkflowInterface; /** * @author Grégoire Pineau @@ -29,6 +35,8 @@ final class WorkflowDataCollector extends DataCollector implements LateDataColle { public function __construct( private readonly iterable $workflows, + private readonly EventDispatcherInterface $eventDispatcher, + private readonly FileLinkFormatter $fileLinkFormatter, ) { } @@ -50,6 +58,7 @@ public function lateCollect(): void $this->data['workflows'][$workflow->getName()] = [ 'dump' => $dumper->dump($workflow->getDefinition()), 'calls' => $calls, + 'listeners' => $this->getEventListeners($workflow), ]; } } @@ -102,4 +111,120 @@ protected function getCasters(): array return $casters; } + + public function hash(string $string): string + { + return hash('xxh128', $string); + } + + private function getEventListeners(WorkflowInterface $workflow): array + { + $listeners = []; + $placeId = 0; + foreach ($workflow->getDefinition()->getPlaces() as $place) { + $eventNames = []; + $subEventNames = [ + 'leave', + 'enter', + 'entered', + ]; + foreach ($subEventNames as $subEventName) { + $eventNames[] = sprintf('workflow.%s', $subEventName); + $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $place); + } + foreach ($eventNames as $eventName) { + foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { + $listeners["place{$placeId}"][$eventName][] = $this->summarizeListener($listener); + } + } + + ++$placeId; + } + + foreach ($workflow->getDefinition()->getTransitions() as $transitionId => $transition) { + $eventNames = []; + $subEventNames = [ + 'guard', + 'transition', + 'completed', + 'announce', + ]; + foreach ($subEventNames as $subEventName) { + $eventNames[] = sprintf('workflow.%s', $subEventName); + $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $transition->getName()); + } + foreach ($eventNames as $eventName) { + foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { + $listeners["transition{$transitionId}"][$eventName][] = $this->summarizeListener($listener, $eventName, $transition); + } + } + } + + return $listeners; + } + + private function summarizeListener(callable $callable, string $eventName = null, Transition $transition = null): array + { + $extra = []; + + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + if (str_contains($r->name, '{closure}')) { + $title = (string) $r; + } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + $title = $class->name.'::'.$r->name.'()'; + } else { + $title = $r->name; + } + } elseif (\is_string($callable)) { + $title = $callable.'()'; + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable) && method_exists($callable, '__invoke')) { + $r = new \ReflectionMethod($callable, '__invoke'); + $title = $callable::class.'::__invoke()'; + } elseif (\is_array($callable)) { + if ($callable[0] instanceof GuardListener) { + if (null === $eventName || null === $transition) { + throw new \LogicException('Missing event name or transition.'); + } + $extra['guardExpressions'] = $this->extractGuardExpressions($callable[0], $eventName, $transition); + } + $r = new \ReflectionMethod($callable[0], $callable[1]); + $title = (\is_string($callable[0]) ? $callable[0] : \get_class($callable[0])).'::'.$callable[1].'()'; + } else { + throw new \RuntimeException('Unknown callable type.'); + } + + $file = null; + if ($r->isUserDefined()) { + $file = $this->fileLinkFormatter->format($r->getFileName(), $r->getStartLine()); + } + + return [ + 'title' => $title, + 'file' => $file, + ...$extra, + ]; + } + + private function extractGuardExpressions(GuardListener $listener, string $eventName, Transition $transition): array + { + $configuration = (new \ReflectionProperty(GuardListener::class, 'configuration'))->getValue($listener); + + $expressions = []; + foreach ($configuration[$eventName] as $guard) { + if ($guard instanceof GuardExpression) { + if ($guard->getTransition() !== $transition) { + continue; + } + $expressions[] = $guard->getExpression(); + } else { + $expressions[] = $guard; + } + } + + return $expressions; + } } diff --git a/Tests/DataCollector/WorkflowDataCollectorTest.php b/Tests/DataCollector/WorkflowDataCollectorTest.php new file mode 100644 index 0000000..21b4fe6 --- /dev/null +++ b/Tests/DataCollector/WorkflowDataCollectorTest.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\DataCollector; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\ErrorHandler\ErrorRenderer\FileLinkFormatter; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; +use Symfony\Component\Validator\Validator\ValidatorInterface; +use Symfony\Component\Workflow\DataCollector\WorkflowDataCollector; +use Symfony\Component\Workflow\EventListener\ExpressionLanguage; +use Symfony\Component\Workflow\EventListener\GuardListener; +use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; +use Symfony\Component\Workflow\Workflow; + +class WorkflowDataCollectorTest extends TestCase +{ + use WorkflowBuilderTrait; + + public function test() + { + $workflow1 = new Workflow($this->createComplexWorkflowDefinition(), name: 'workflow1'); + $workflow2 = new Workflow($this->createSimpleWorkflowDefinition(), name: 'workflow2'); + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('workflow.workflow2.leave.a', fn () => true); + $dispatcher->addListener('workflow.workflow2.leave.a', [self::class, 'noop']); + $dispatcher->addListener('workflow.workflow2.leave.a', [$this, 'noop']); + $dispatcher->addListener('workflow.workflow2.leave.a', $this->noop(...)); + $dispatcher->addListener('workflow.workflow2.leave.a', 'var_dump'); + $guardListener = new GuardListener( + ['workflow.workflow2.guard.t1' => ['my_expression']], + $this->createMock(ExpressionLanguage::class), + $this->createMock(TokenStorageInterface::class), + $this->createMock(AuthorizationCheckerInterface::class), + $this->createMock(AuthenticationTrustResolverInterface::class), + $this->createMock(RoleHierarchyInterface::class), + $this->createMock(ValidatorInterface::class) + ); + $dispatcher->addListener('workflow.workflow2.guard.t1', [$guardListener, 'onTransition']); + + $collector = new WorkflowDataCollector( + [$workflow1, $workflow2], + $dispatcher, + new FileLinkFormatter(), + ); + + $collector->lateCollect(); + + $data = $collector->getWorkflows(); + + $this->assertArrayHasKey('workflow1', $data); + $this->assertArrayHasKey('dump', $data['workflow1']); + $this->assertStringStartsWith("graph LR\n", $data['workflow1']['dump']); + $this->assertArrayHasKey('listeners', $data['workflow1']); + + $this->assertSame([], $data['workflow1']['listeners']); + $this->assertArrayHasKey('workflow2', $data); + $this->assertArrayHasKey('dump', $data['workflow2']); + $this->assertStringStartsWith("graph LR\n", $data['workflow1']['dump']); + $this->assertArrayHasKey('listeners', $data['workflow2']); + $listeners = $data['workflow2']['listeners']; + $this->assertArrayHasKey('place0', $listeners); + $this->assertArrayHasKey('workflow.workflow2.leave.a', $listeners['place0']); + $descriptions = $listeners['place0']['workflow.workflow2.leave.a']; + $this->assertCount(5, $descriptions); + $this->assertStringContainsString('Closure', $descriptions[0]['title']); + $this->assertSame('Symfony\Component\Workflow\Tests\DataCollector\WorkflowDataCollectorTest::noop()', $descriptions[1]['title']); + $this->assertSame('Symfony\Component\Workflow\Tests\DataCollector\WorkflowDataCollectorTest::noop()', $descriptions[2]['title']); + $this->assertSame('Symfony\Component\Workflow\Tests\DataCollector\WorkflowDataCollectorTest::noop()', $descriptions[3]['title']); + $this->assertSame('var_dump()', $descriptions[4]['title']); + $this->assertArrayHasKey('transition0', $listeners); + $this->assertArrayHasKey('workflow.workflow2.guard.t1', $listeners['transition0']); + $this->assertSame('Symfony\Component\Workflow\EventListener\GuardListener::onTransition()', $listeners['transition0']['workflow.workflow2.guard.t1'][0]['title']); + $this->assertSame(['my_expression'], $listeners['transition0']['workflow.workflow2.guard.t1'][0]['guardExpressions']); + } + + public static function noop() + { + } +} diff --git a/composer.json b/composer.json index 3a95fdd..6892198 100644 --- a/composer.json +++ b/composer.json @@ -26,8 +26,10 @@ "require-dev": { "psr/log": "^1|^2|^3", "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/security-core": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0" }, From ba50c4efff43256711a4dc865b83122c3b4ef8a2 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Fri, 27 Oct 2023 12:31:04 +0200 Subject: [PATCH 107/145] DX: re-apply concat_space --- Tests/Dumper/PlantUmlDumperTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Dumper/PlantUmlDumperTest.php b/Tests/Dumper/PlantUmlDumperTest.php index 3b0d010..a018a4e 100644 --- a/Tests/Dumper/PlantUmlDumperTest.php +++ b/Tests/Dumper/PlantUmlDumperTest.php @@ -96,6 +96,6 @@ public function testDumpWorkflowWithSpacesInTheStateNamesAndDescription() private function getFixturePath($name, $transitionType): string { - return __DIR__ . '/../Fixtures/puml/' .$transitionType.'/'.$name.'.puml'; + return __DIR__.'/../Fixtures/puml/'.$transitionType.'/'.$name.'.puml'; } } From 3c34806f7b143a64ce8a12a93c93a7f1cd101256 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 25 Nov 2023 21:15:12 +0100 Subject: [PATCH 108/145] Remove obsolete PHP version checks --- DataCollector/WorkflowDataCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index 656594d..6f13a17 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -173,7 +173,7 @@ private function summarizeListener(callable $callable, string $eventName = null, $r = new \ReflectionFunction($callable); if (str_contains($r->name, '{closure}')) { $title = (string) $r; - } elseif ($class = \PHP_VERSION_ID >= 80111 ? $r->getClosureCalledClass() : $r->getClosureScopeClass()) { + } elseif ($class = $r->getClosureCalledClass()) { $title = $class->name.'::'.$r->name.'()'; } else { $title = $r->name; From 06bcb0385811a604131eab6187b04e66844c9b03 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 30 Nov 2023 09:55:50 +0100 Subject: [PATCH 109/145] [Workflow] Add `getEnabledTransition()` to TraceableWorkflow --- Debug/TraceableWorkflow.php | 6 ++ Tests/Debug/TraceableWorkflowTest.php | 100 ++++++++++++++++++++++++++ composer.json | 1 + 3 files changed, 107 insertions(+) create mode 100644 Tests/Debug/TraceableWorkflowTest.php diff --git a/Debug/TraceableWorkflow.php b/Debug/TraceableWorkflow.php index b8ac086..6d0afd8 100644 --- a/Debug/TraceableWorkflow.php +++ b/Debug/TraceableWorkflow.php @@ -16,6 +16,7 @@ use Symfony\Component\Workflow\Marking; use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; +use Symfony\Component\Workflow\Transition; use Symfony\Component\Workflow\TransitionBlockerList; use Symfony\Component\Workflow\WorkflowInterface; @@ -57,6 +58,11 @@ public function getEnabledTransitions(object $subject): array return $this->callInner(__FUNCTION__, \func_get_args()); } + public function getEnabledTransition(object $subject, string $name): ?Transition + { + return $this->callInner(__FUNCTION__, \func_get_args()); + } + public function getName(): string { return $this->workflow->getName(); diff --git a/Tests/Debug/TraceableWorkflowTest.php b/Tests/Debug/TraceableWorkflowTest.php new file mode 100644 index 0000000..5bfcee9 --- /dev/null +++ b/Tests/Debug/TraceableWorkflowTest.php @@ -0,0 +1,100 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\Debug; + +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\Workflow\Debug\TraceableWorkflow; +use Symfony\Component\Workflow\Marking; +use Symfony\Component\Workflow\TransitionBlockerList; +use Symfony\Component\Workflow\Workflow; + +class TraceableWorkflowTest extends TestCase +{ + private MockObject|Workflow $innerWorkflow; + + private StopWatch $stopwatch; + + private TraceableWorkflow $traceableWorkflow; + + protected function setUp(): void + { + $this->innerWorkflow = $this->createMock(Workflow::class); + $this->stopwatch = new Stopwatch(); + + $this->traceableWorkflow = new TraceableWorkflow( + $this->innerWorkflow, + $this->stopwatch + ); + } + + /** + * @dataProvider provideFunctionNames + */ + public function testCallsInner(string $function, array $args, mixed $returnValue) + { + $this->innerWorkflow->expects($this->once()) + ->method($function) + ->willReturn($returnValue); + + $this->assertSame($returnValue, $this->traceableWorkflow->{$function}(...$args)); + + $calls = $this->traceableWorkflow->getCalls(); + + $this->assertCount(1, $calls); + $this->assertSame($function, $calls[0]['method']); + $this->assertArrayHasKey('duration', $calls[0]); + $this->assertSame($returnValue, $calls[0]['return']); + } + + public function testCallsInnerCatchesException() + { + $exception = new \Exception('foo'); + $this->innerWorkflow->expects($this->once()) + ->method('can') + ->willThrowException($exception); + + try { + $this->traceableWorkflow->can(new \stdClass(), 'foo'); + + $this->fail('An exception should have been thrown.'); + } catch (\Exception $e) { + $this->assertSame($exception, $e); + + $calls = $this->traceableWorkflow->getCalls(); + + $this->assertCount(1, $calls); + $this->assertSame('can', $calls[0]['method']); + $this->assertArrayHasKey('duration', $calls[0]); + $this->assertArrayHasKey('exception', $calls[0]); + $this->assertSame($exception, $calls[0]['exception']); + } + } + + public static function provideFunctionNames(): \Generator + { + $subject = new \stdClass(); + + yield ['getMarking', [$subject], new Marking(['place' => 1])]; + + yield ['can', [$subject, 'foo'], true]; + + yield ['buildTransitionBlockerList', [$subject, 'foo'], new TransitionBlockerList()]; + + yield ['apply', [$subject, 'foo'], new Marking(['place' => 1])]; + + yield ['getEnabledTransitions', [$subject], []]; + + yield ['getEnabledTransition', [$subject, 'foo'], null]; + } +} diff --git a/composer.json b/composer.json index 6892198..2c277fc 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", "symfony/validator": "^5.4|^6.0|^7.0" }, "conflict": { From 1a7ed3511fe247398de4cb4b7c4130823ecd6f2d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 1 Dec 2023 16:42:06 +0100 Subject: [PATCH 110/145] [Workflow] Add `getEnabledTransition()` method annotation to WorkflowInterface --- CHANGELOG.md | 5 +++++ WorkflowInterface.php | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00840ac..f8b83a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +7.1 +--- + + * Add method `getEnabledTransition()` to `WorkflowInterface` + 7.0 --- diff --git a/WorkflowInterface.php b/WorkflowInterface.php index 17aa7e0..8e0faef 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -19,6 +19,8 @@ * Describes a workflow instance. * * @author Amrouche Hamza + * + * @method Transition|null getEnabledTransition(object $subject, string $name) */ interface WorkflowInterface { From 2af9c8ea0a01d2bc23d85d1ac91ef07281c98a2d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Thu, 14 Dec 2023 11:03:37 +0100 Subject: [PATCH 111/145] Set `strict` parameter of `in_array` to true where possible --- Dumper/MermaidDumper.php | 2 +- Validator/WorkflowValidator.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 2d0f958..53436a1 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -72,7 +72,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt $placeId, $place, $meta->getPlaceMetadata($place), - \in_array($place, $definition->getInitialPlaces()), + \in_array($place, $definition->getInitialPlaces(), true), $marking?->has($place) ?? false ); diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index 3f88d11..2afefb7 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -33,7 +33,7 @@ public function validate(Definition $definition, string $name): void $places = array_fill_keys($definition->getPlaces(), []); foreach ($definition->getTransitions() as $transition) { foreach ($transition->getFroms() as $from) { - if (\in_array($transition->getName(), $places[$from])) { + if (\in_array($transition->getName(), $places[$from], true)) { throw new InvalidDefinitionException(sprintf('All transitions for a place must have an unique name. Multiple transitions named "%s" where found for place "%s" in workflow "%s".', $transition->getName(), $from, $name)); } $places[$from][] = $transition->getName(); From b93c436702b2db1c098598dc38a7923ace722403 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Thu, 21 Dec 2023 00:06:49 +0100 Subject: [PATCH 112/145] [Workflow] Fix test --- Tests/Validator/StateMachineValidatorTest.php | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/Tests/Validator/StateMachineValidatorTest.php b/Tests/Validator/StateMachineValidatorTest.php index 5157b4d..e88408b 100644 --- a/Tests/Validator/StateMachineValidatorTest.php +++ b/Tests/Validator/StateMachineValidatorTest.php @@ -116,27 +116,13 @@ public function testValid() public function testWithTooManyInitialPlaces() { - $this->expectException(InvalidDefinitionException::class); - $this->expectExceptionMessage('The state machine "foo" cannot store many places. But the definition has 2 initial places. Only one is supported.'); $places = range('a', 'c'); $transitions = []; $definition = new Definition($places, $transitions, ['a', 'b']); - (new StateMachineValidator())->validate($definition, 'foo'); - - // the test ensures that the validation does not fail (i.e. it does not throw any exceptions) - $this->addToAssertionCount(1); + $this->expectException(InvalidDefinitionException::class); + $this->expectExceptionMessage('The state machine "foo" cannot store many places. But the definition has 2 initial places. Only one is supported.'); - // The graph looks like: - // - // +----+ +----+ +---+ - // | a | --> | t1 | --> | b | - // +----+ +----+ +---+ - // | - // | - // v - // +----+ +----+ - // | t2 | --> | c | - // +----+ +----+ + (new StateMachineValidator())->validate($definition, 'foo'); } } From f83393890ae88b2e8b5ee747921ab9a46e2aaaf1 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 1 Nov 2023 09:14:07 +0100 Subject: [PATCH 113/145] [Tests] Streamline --- Tests/DefinitionTest.php | 6 ++++-- Tests/RegistryTest.php | 8 ++------ Tests/Validator/WorkflowValidatorTest.php | 5 +++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index 9e9c783..3dc40dd 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -64,18 +64,20 @@ public function testAddTransition() public function testAddTransitionAndFromPlaceIsNotDefined() { + $places = range('a', 'b'); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); - $places = range('a', 'b'); new Definition($places, [new Transition('name', 'c', $places[1])]); } public function testAddTransitionAndToPlaceIsNotDefined() { + $places = range('a', 'b'); + $this->expectException(LogicException::class); $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); - $places = range('a', 'b'); new Definition($places, [new Transition('name', $places[0], 'c')]); } diff --git a/Tests/RegistryTest.php b/Tests/RegistryTest.php index f9a8fe0..d3282a8 100644 --- a/Tests/RegistryTest.php +++ b/Tests/RegistryTest.php @@ -63,18 +63,14 @@ public function testGetWithMultipleMatch() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Too many workflows (workflow2, workflow3) match this subject (Symfony\Component\Workflow\Tests\Subject2); set a different name on each and use the second (name) argument of this method.'); - $w1 = $this->registry->get(new Subject2()); - $this->assertInstanceOf(Workflow::class, $w1); - $this->assertSame('workflow1', $w1->getName()); + $this->registry->get(new Subject2()); } public function testGetWithNoMatch() { $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Unable to find a workflow for class "stdClass".'); - $w1 = $this->registry->get(new \stdClass()); - $this->assertInstanceOf(Workflow::class, $w1); - $this->assertSame('workflow1', $w1->getName()); + $this->registry->get(new \stdClass()); } public function testAllWithOneMatchWithSuccess() diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index 036ece7..34eeda6 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -24,8 +24,6 @@ class WorkflowValidatorTest extends TestCase public function testWorkflowWithInvalidNames() { - $this->expectException(InvalidDefinitionException::class); - $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); $places = range('a', 'c'); $transitions = []; @@ -35,6 +33,9 @@ public function testWorkflowWithInvalidNames() $definition = new Definition($places, $transitions); + $this->expectException(InvalidDefinitionException::class); + $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); + (new WorkflowValidator())->validate($definition, 'foo'); } From 30ef7edfaffe0be6c3836a714c5c3297dee3a4ca Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Fri, 29 Dec 2023 21:48:46 +0100 Subject: [PATCH 114/145] [Workflow] Link to class in PHPDoc --- Workflow.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Workflow.php b/Workflow.php index 3d4e856..6ab02db 100644 --- a/Workflow.php +++ b/Workflow.php @@ -59,9 +59,9 @@ class Workflow implements WorkflowInterface /** * When `null` fire all events (the default behaviour). - * Setting this to an empty array `[]` means no events are dispatched (except the Guard Event). + * Setting this to an empty array `[]` means no events are dispatched (except the {@see GuardEvent}). * Passing an array with WorkflowEvents will allow only those events to be dispatched plus - * the Guard Event. + * the {@see GuardEvent}. * * @var array|string[]|null */ From 9702e31449ea7c0fb2fc9d18d96e63941d7d7b44 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 30 Dec 2023 20:21:21 +0100 Subject: [PATCH 115/145] Leverage ReflectionFunction::isAnonymous() --- DataCollector/WorkflowDataCollector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index 6f13a17..821b071 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -171,7 +171,7 @@ private function summarizeListener(callable $callable, string $eventName = null, if ($callable instanceof \Closure) { $r = new \ReflectionFunction($callable); - if (str_contains($r->name, '{closure}')) { + if ($r->isAnonymous()) { $title = (string) $r; } elseif ($class = $r->getClosureCalledClass()) { $title = $class->name.'::'.$r->name.'()'; From 1710d1faf72111c5a431ee388facc17622d4026d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 10 Oct 2023 16:04:32 +0200 Subject: [PATCH 116/145] [Console][EventDispatcher][Security][Serializer][Workflow] Add PHPDoc to attribute classes and properties --- Attribute/AsAnnounceListener.php | 9 +++++++++ Attribute/AsCompletedListener.php | 9 +++++++++ Attribute/AsEnterListener.php | 9 +++++++++ Attribute/AsEnteredListener.php | 9 +++++++++ Attribute/AsGuardListener.php | 9 +++++++++ Attribute/AsLeaveListener.php | 9 +++++++++ Attribute/AsTransitionListener.php | 9 +++++++++ 7 files changed, 63 insertions(+) diff --git a/Attribute/AsAnnounceListener.php b/Attribute/AsAnnounceListener.php index 01669dc..7881fc7 100644 --- a/Attribute/AsAnnounceListener.php +++ b/Attribute/AsAnnounceListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for the "announce" event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsAnnounceListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $transition The transition name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same transition + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $transition = null, diff --git a/Attribute/AsCompletedListener.php b/Attribute/AsCompletedListener.php index 012b304..dd0f024 100644 --- a/Attribute/AsCompletedListener.php +++ b/Attribute/AsCompletedListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for the "completed" event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsCompletedListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $transition The transition name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same transition + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $transition = null, diff --git a/Attribute/AsEnterListener.php b/Attribute/AsEnterListener.php index fe55f6e..0897f7d 100644 --- a/Attribute/AsEnterListener.php +++ b/Attribute/AsEnterListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for the "enter" event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsEnterListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $place The place name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same place + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $place = null, diff --git a/Attribute/AsEnteredListener.php b/Attribute/AsEnteredListener.php index 474cf09..8ab6a94 100644 --- a/Attribute/AsEnteredListener.php +++ b/Attribute/AsEnteredListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for the "entered" event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsEnteredListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $place The place name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same place + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $place = null, diff --git a/Attribute/AsGuardListener.php b/Attribute/AsGuardListener.php index 994fe32..f9c17f4 100644 --- a/Attribute/AsGuardListener.php +++ b/Attribute/AsGuardListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for a guard event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsGuardListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $transition The transition name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same transition + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $transition = null, diff --git a/Attribute/AsLeaveListener.php b/Attribute/AsLeaveListener.php index e4ea4dc..2e68da1 100644 --- a/Attribute/AsLeaveListener.php +++ b/Attribute/AsLeaveListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for the "leave" event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsLeaveListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $place The place name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same place + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $place = null, diff --git a/Attribute/AsTransitionListener.php b/Attribute/AsTransitionListener.php index 589ef7a..f401312 100644 --- a/Attribute/AsTransitionListener.php +++ b/Attribute/AsTransitionListener.php @@ -14,6 +14,8 @@ use Symfony\Component\EventDispatcher\Attribute\AsEventListener; /** + * Defines a listener for a transition event of a workflow. + * * @author Grégoire Pineau */ #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] @@ -21,6 +23,13 @@ final class AsTransitionListener extends AsEventListener { use BuildEventNameTrait; + /** + * @param string|null $workflow The id of the workflow to listen to + * @param string|null $transition The transition name to which the listener listens to + * @param string|null $method The method to run when the listened event is triggered + * @param int $priority The priority of this listener if several are declared for the same transition + * @param string|null $dispatcher The service id of the event dispatcher to listen to + */ public function __construct( string $workflow = null, string $transition = null, From ff424ed4736ebc305784386208b5e91085b4500e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 Jan 2024 14:51:25 +0100 Subject: [PATCH 117/145] Apply php-cs-fixer fix --rules nullable_type_declaration_for_default_null_value --- Definition.php | 2 +- Dumper/DumperInterface.php | 2 +- Dumper/GraphvizDumper.php | 4 ++-- Dumper/MermaidDumper.php | 2 +- Dumper/PlantUmlDumper.php | 6 +++--- Dumper/StateMachineGraphvizDumper.php | 2 +- Event/Event.php | 2 +- Event/GuardEvent.php | 4 ++-- EventListener/GuardListener.php | 2 +- Metadata/InMemoryMetadataStore.php | 2 +- Registry.php | 4 ++-- StateMachine.php | 2 +- Tests/EventListener/GuardListenerTest.php | 2 +- Tests/WorkflowTest.php | 2 +- TransitionBlocker.php | 2 +- Workflow.php | 2 +- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Definition.php b/Definition.php index 1233538..5be31f9 100644 --- a/Definition.php +++ b/Definition.php @@ -32,7 +32,7 @@ final class Definition * @param Transition[] $transitions * @param string|string[]|null $initialPlaces */ - public function __construct(array $places, array $transitions, $initialPlaces = null, MetadataStoreInterface $metadataStore = null) + public function __construct(array $places, array $transitions, $initialPlaces = null, ?MetadataStoreInterface $metadataStore = null) { foreach ($places as $place) { $this->addPlace($place); diff --git a/Dumper/DumperInterface.php b/Dumper/DumperInterface.php index 19f04b0..9186aa7 100644 --- a/Dumper/DumperInterface.php +++ b/Dumper/DumperInterface.php @@ -27,5 +27,5 @@ interface DumperInterface * * @return string */ - public function dump(Definition $definition, Marking $marking = null, array $options = []); + public function dump(Definition $definition, ?Marking $marking = null, array $options = []); } diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 56623b5..9c79f82 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -44,7 +44,7 @@ class GraphvizDumper implements DumperInterface * * node: The default options for nodes (places + transitions) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []) + public function dump(Definition $definition, ?Marking $marking = null, array $options = []) { $places = $this->findPlaces($definition, $marking); $transitions = $this->findTransitions($definition); @@ -62,7 +62,7 @@ public function dump(Definition $definition, Marking $marking = null, array $opt /** * @internal */ - protected function findPlaces(Definition $definition, Marking $marking = null): array + protected function findPlaces(Definition $definition, ?Marking $marking = null): array { $workflowMetadata = $definition->getMetadataStore(); diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 9f6a5b5..11c89d3 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -66,7 +66,7 @@ public function __construct(string $transitionType, string $direction = self::DI $this->transitionType = $transitionType; } - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $this->linkCount = 0; $placeNameMap = []; diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index d854846..72911a3 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -53,7 +53,7 @@ class PlantUmlDumper implements DumperInterface private $transitionType = self::STATEMACHINE_TRANSITION; - public function __construct(string $transitionType = null) + public function __construct(?string $transitionType = null) { if (!\in_array($transitionType, self::TRANSITION_TYPES, true)) { throw new \InvalidArgumentException("Transition type '$transitionType' does not exist."); @@ -61,7 +61,7 @@ public function __construct(string $transitionType = null) $this->transitionType = $transitionType; } - public function dump(Definition $definition, Marking $marking = null, array $options = []): string + public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string { $options = array_replace_recursive(self::DEFAULT_OPTIONS, $options); @@ -191,7 +191,7 @@ private function escape(string $string): string return '"'.str_replace('"', '', $string).'"'; } - private function getState(string $place, Definition $definition, Marking $marking = null): string + private function getState(string $place, Definition $definition, ?Marking $marking = null): string { $workflowMetadata = $definition->getMetadataStore(); diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index 4bd818d..3ea6d76 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -27,7 +27,7 @@ class StateMachineGraphvizDumper extends GraphvizDumper * * node: The default options for nodes (places) * * edge: The default options for edges */ - public function dump(Definition $definition, Marking $marking = null, array $options = []) + public function dump(Definition $definition, ?Marking $marking = null, array $options = []) { $places = $this->findPlaces($definition, $marking); $edges = $this->findEdges($definition); diff --git a/Event/Event.php b/Event/Event.php index e1f448a..cd59d03 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -29,7 +29,7 @@ class Event extends BaseEvent private $transition; private $workflow; - public function __construct(object $subject, Marking $marking, Transition $transition = null, WorkflowInterface $workflow = null, array $context = []) + public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) { $this->subject = $subject; $this->marking = $marking; diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index 039d161..11df6cc 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -28,7 +28,7 @@ final class GuardEvent extends Event /** * {@inheritdoc} */ - public function __construct(object $subject, Marking $marking, Transition $transition, WorkflowInterface $workflow = null) + public function __construct(object $subject, Marking $marking, Transition $transition, ?WorkflowInterface $workflow = null) { parent::__construct($subject, $marking, $transition, $workflow); @@ -45,7 +45,7 @@ public function isBlocked(): bool return !$this->transitionBlockerList->isEmpty(); } - public function setBlocked(bool $blocked, string $message = null): void + public function setBlocked(bool $blocked, ?string $message = null): void { if (!$blocked) { $this->transitionBlockerList->clear(); diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 8b63f93..0162996 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -32,7 +32,7 @@ class GuardListener private $roleHierarchy; private $validator; - public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, RoleHierarchyInterface $roleHierarchy = null, ValidatorInterface $validator = null) + public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, ?RoleHierarchyInterface $roleHierarchy = null, ?ValidatorInterface $validator = null) { $this->configuration = $configuration; $this->expressionLanguage = $expressionLanguage; diff --git a/Metadata/InMemoryMetadataStore.php b/Metadata/InMemoryMetadataStore.php index 8fdc9e7..e072ac3 100644 --- a/Metadata/InMemoryMetadataStore.php +++ b/Metadata/InMemoryMetadataStore.php @@ -27,7 +27,7 @@ final class InMemoryMetadataStore implements MetadataStoreInterface /** * @param \SplObjectStorage|null $transitionsMetadata */ - public function __construct(array $workflowMetadata = [], array $placesMetadata = [], \SplObjectStorage $transitionsMetadata = null) + public function __construct(array $workflowMetadata = [], array $placesMetadata = [], ?\SplObjectStorage $transitionsMetadata = null) { $this->workflowMetadata = $workflowMetadata; $this->placesMetadata = $placesMetadata; diff --git a/Registry.php b/Registry.php index 3474e95..85aa36f 100644 --- a/Registry.php +++ b/Registry.php @@ -27,7 +27,7 @@ public function addWorkflow(WorkflowInterface $workflow, WorkflowSupportStrategy $this->workflows[] = [$workflow, $supportStrategy]; } - public function has(object $subject, string $workflowName = null): bool + public function has(object $subject, ?string $workflowName = null): bool { foreach ($this->workflows as [$workflow, $supportStrategy]) { if ($this->supports($workflow, $supportStrategy, $subject, $workflowName)) { @@ -41,7 +41,7 @@ public function has(object $subject, string $workflowName = null): bool /** * @return Workflow */ - public function get(object $subject, string $workflowName = null) + public function get(object $subject, ?string $workflowName = null) { $matched = []; diff --git a/StateMachine.php b/StateMachine.php index 8fb4d3b..0946307 100644 --- a/StateMachine.php +++ b/StateMachine.php @@ -20,7 +20,7 @@ */ class StateMachine extends Workflow { - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) + public function __construct(Definition $definition, ?MarkingStoreInterface $markingStore = null, ?EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', ?array $eventsToDispatch = null) { parent::__construct($definition, $markingStore ?? new MethodMarkingStore(true), $dispatcher, $name, $eventsToDispatch); } diff --git a/Tests/EventListener/GuardListenerTest.php b/Tests/EventListener/GuardListenerTest.php index 9636402..8eb0a77 100644 --- a/Tests/EventListener/GuardListenerTest.php +++ b/Tests/EventListener/GuardListenerTest.php @@ -148,7 +148,7 @@ public function testGuardExpressionBlocks() $this->assertTrue($event->isBlocked()); } - private function createEvent(Transition $transition = null) + private function createEvent(?Transition $transition = null) { $subject = new Subject(); $transition = $transition ?? new Transition('name', 'from', 'to'); diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 6d84b19..a109655 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -791,7 +791,7 @@ class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDis { public $dispatchedEvents = []; - public function dispatch($event, string $eventName = null): object + public function dispatch($event, ?string $eventName = null): object { $this->dispatchedEvents[] = $eventName; diff --git a/TransitionBlocker.php b/TransitionBlocker.php index 9e52cc9..233cf4d 100644 --- a/TransitionBlocker.php +++ b/TransitionBlocker.php @@ -67,7 +67,7 @@ public static function createBlockedByExpressionGuardListener(string $expression * Creates a blocker that says the transition cannot be made because of an * unknown reason. */ - public static function createUnknown(string $message = null, int $backtraceFrame = 2): self + public static function createUnknown(?string $message = null, int $backtraceFrame = 2): self { if (null !== $message) { return new static($message, self::UNKNOWN); diff --git a/Workflow.php b/Workflow.php index 12a0bab..965face 100644 --- a/Workflow.php +++ b/Workflow.php @@ -67,7 +67,7 @@ class Workflow implements WorkflowInterface */ private $eventsToDispatch = null; - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', array $eventsToDispatch = null) + public function __construct(Definition $definition, ?MarkingStoreInterface $markingStore = null, ?EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', ?array $eventsToDispatch = null) { $this->definition = $definition; $this->markingStore = $markingStore ?? new MethodMarkingStore(); From a7717b6d3a9a2ef6dd817439c7265619574be920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 8 Feb 2024 18:09:44 +0100 Subject: [PATCH 118/145] [workflow] determines places form transitions --- CHANGELOG.md | 1 + Definition.php | 10 ++++------ Tests/DefinitionTest.php | 12 ++++-------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8b83a5..247d5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add method `getEnabledTransition()` to `WorkflowInterface` + * Automatically register places from transitions 7.0 --- diff --git a/Definition.php b/Definition.php index 58456bb..96ec1fe 100644 --- a/Definition.php +++ b/Definition.php @@ -104,17 +104,15 @@ private function addPlace(string $place): void private function addTransition(Transition $transition): void { - $name = $transition->getName(); - foreach ($transition->getFroms() as $from) { - if (!isset($this->places[$from])) { - throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $from, $name)); + if (!\array_key_exists($from, $this->places)) { + $this->addPlace($from); } } foreach ($transition->getTos() as $to) { - if (!isset($this->places[$to])) { - throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $to, $name)); + if (!\array_key_exists($to, $this->places)) { + $this->addPlace($to); } } diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index 3dc40dd..4303dee 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -66,19 +66,15 @@ public function testAddTransitionAndFromPlaceIsNotDefined() { $places = range('a', 'b'); - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); - - new Definition($places, [new Transition('name', 'c', $places[1])]); + $definition = new Definition($places, [new Transition('name', 'c', $places[1])]); + $this->assertContains('c', $definition->getPlaces()); } public function testAddTransitionAndToPlaceIsNotDefined() { $places = range('a', 'b'); - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Place "c" referenced in transition "name" does not exist.'); - - new Definition($places, [new Transition('name', $places[0], 'c')]); + $definition = new Definition($places, [new Transition('name', $places[0], 'c')]); + $this->assertContains('c', $definition->getPlaces()); } } From 99213130945848f8ade43e338237a4f8c2cca8be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 8 Feb 2024 17:44:38 +0100 Subject: [PATCH 119/145] [Workflow] Fix Marking when it must contains more than one tokens --- Marking.php | 37 ++++++++++++--- Tests/MarkingTest.php | 54 +++++++++++++++++++-- Tests/WorkflowBuilderTrait.php | 39 ++++++++++++++++ Tests/WorkflowTest.php | 85 +++++++++++++++++++++++++++++----- 4 files changed, 192 insertions(+), 23 deletions(-) diff --git a/Marking.php b/Marking.php index 4396c2f..55abef2 100644 --- a/Marking.php +++ b/Marking.php @@ -19,7 +19,7 @@ class Marking { private $places = []; - private $context = null; + private $context; /** * @param int[] $representation Keys are the place name and values should be 1 @@ -27,23 +27,46 @@ class Marking public function __construct(array $representation = []) { foreach ($representation as $place => $nbToken) { - $this->mark($place); + $this->mark($place, $nbToken); } } - public function mark(string $place) + public function mark(string $place, int $nbToken = 1) { - $this->places[$place] = 1; + if ($nbToken < 1) { + throw new \LogicException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + } + + if (!\array_key_exists($place, $this->places)) { + $this->places[$place] = 0; + } + $this->places[$place] += $nbToken; } - public function unmark(string $place) + public function unmark(string $place, int $nbToken = 1) { - unset($this->places[$place]); + if ($nbToken < 1) { + throw new \LogicException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + } + + if (!$this->has($place)) { + throw new \LogicException(sprintf('The place "%s" is not marked.', $place)); + } + + $this->places[$place] -= $nbToken; + + if (0 > $this->places[$place]) { + throw new \LogicException(sprintf('The place "%s" could not contain a negative token number.', $place)); + } + + if (0 === $this->places[$place]) { + unset($this->places[$place]); + } } public function has(string $place) { - return isset($this->places[$place]); + return \array_key_exists($place, $this->places); } public function getPlaces() diff --git a/Tests/MarkingTest.php b/Tests/MarkingTest.php index 0a1c22b..fc06b72 100644 --- a/Tests/MarkingTest.php +++ b/Tests/MarkingTest.php @@ -22,24 +22,70 @@ public function testMarking() $this->assertTrue($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertSame(['a' => 1], $marking->getPlaces()); + $this->assertPlaces(['a' => 1], $marking); $marking->mark('b'); $this->assertTrue($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertSame(['a' => 1, 'b' => 1], $marking->getPlaces()); + $this->assertPlaces(['a' => 1, 'b' => 1], $marking); $marking->unmark('a'); $this->assertFalse($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertSame(['b' => 1], $marking->getPlaces()); + $this->assertPlaces(['b' => 1], $marking); $marking->unmark('b'); $this->assertFalse($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertSame([], $marking->getPlaces()); + $this->assertPlaces([], $marking); + + $marking->mark('a'); + $this->assertPlaces(['a' => 1], $marking); + + $marking->mark('a'); + $this->assertPlaces(['a' => 2], $marking); + + $marking->unmark('a'); + $this->assertPlaces(['a' => 1], $marking); + + $marking->unmark('a'); + $this->assertPlaces([], $marking); + } + + public function testGuardNotMarked() + { + $marking = new Marking([]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The place "a" is not marked.'); + $marking->unmark('a'); + } + + public function testGuardNotNbTokenLowerThanZero() + { + $marking = new Marking(['a' => 1]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The place "a" could not contain a negative token number.'); + $marking->unmark('a', 2); + } + + public function testGuardNotNbTokenEquals0() + { + $marking = new Marking(['a' => 1]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The number of tokens must be greater than 0, "0" given.'); + $marking->unmark('a', 0); + } + + private function assertPlaces(array $expected, Marking $marking) + { + $places = $marking->getPlaces(); + ksort($places); + $this->assertSame($expected, $places); } } diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index ae81232..5e825a4 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -158,4 +158,43 @@ private static function createComplexStateMachineDefinition() // | d | -------------+ // +-----+ } + + private static function createWorkflowWithSameNameBackTransition(): Definition + { + $places = range('a', 'c'); + + $transitions = []; + $transitions[] = new Transition('a_to_bc', 'a', ['b', 'c']); + $transitions[] = new Transition('back1', 'b', 'a'); + $transitions[] = new Transition('back1', 'c', 'b'); + $transitions[] = new Transition('back2', 'c', 'b'); + $transitions[] = new Transition('back2', 'b', 'a'); + $transitions[] = new Transition('c_to_cb', 'c', ['b', 'c']); + + return new Definition($places, $transitions); + + // The graph looks like: + // +-----------------------------------------------------------------+ + // | | + // | | + // | +---------------------------------------------+ | + // v | v | + // +---+ +---------+ +-------+ +---------+ +---+ +-------+ + // | a | --> | a_to_bc | --> | | --> | back2 | --> | | --> | back2 | + // +---+ +---------+ | | +---------+ | | +-------+ + // ^ | | | | + // | | c | <-----+ | b | + // | | | | | | + // | | | +---------+ | | +-------+ + // | | | --> | c_to_cb | --> | | --> | back1 | + // | +-------+ +---------+ +---+ +-------+ + // | | ^ | + // | | | | + // | v | | + // | +-------+ | | + // | | back1 | ----------------------+ | + // | +-------+ | + // | | + // +-----------------------------------------------------------------+ + } } diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index a109655..0ed3602 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -330,28 +330,32 @@ public function testApplyWithSameNameTransition() $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertFalse($marking->has('a')); - $this->assertTrue($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertPlaces([ + 'b' => 1, + 'c' => 1, + ], $marking); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + // Two tokens in "a" + $this->assertPlaces([ + 'a' => 2, + ], $marking); $workflow->apply($subject, 'a_to_bc'); $marking = $workflow->apply($subject, 'b_to_c'); - $this->assertFalse($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertPlaces([ + 'a' => 1, + 'c' => 2, + ], $marking); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + $this->assertPlaces([ + 'a' => 2, + 'c' => 1, + ], $marking); } public function testApplyWithSameNameTransition2() @@ -785,6 +789,63 @@ public function testGetEnabledTransitionsWithSameNameTransition() $this->assertSame('to_a', $transitions[1]->getName()); $this->assertSame('to_a', $transitions[2]->getName()); } + + /** + * @@testWith ["back1"] + * ["back2"] + */ + public function testApplyWithSameNameBackTransition(string $transition) + { + $definition = $this->createWorkflowWithSameNameBackTransition(); + $workflow = new Workflow($definition, new MethodMarkingStore()); + + $subject = new Subject(); + + $marking = $workflow->apply($subject, 'a_to_bc'); + $this->assertPlaces([ + 'b' => 1, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, $transition); + $this->assertPlaces([ + 'a' => 1, + 'b' => 1, + ], $marking); + + $marking = $workflow->apply($subject, $transition); + $this->assertPlaces([ + 'a' => 2, + ], $marking); + + $marking = $workflow->apply($subject, 'a_to_bc'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 1, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, 'c_to_cb'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 2, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, 'c_to_cb'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 3, + 'c' => 1, + ], $marking); + } + + private function assertPlaces(array $expected, Marking $marking) + { + $places = $marking->getPlaces(); + ksort($places); + $this->assertSame($expected, $places); + } } class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface From b639f6856e2e29cc02339d0b20710040ea295069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 12 Mar 2024 09:06:46 +0100 Subject: [PATCH 120/145] Revert "bug #53865 [Workflow]Fix Marking when it must contains more than one tokens (lyrixx)" This reverts commit 5ce412b6f772d78185557bc8b36848615c583d20, reversing changes made to 7dae80db975ccdba768ef67b2f1a8b7c195bf7e9. --- Marking.php | 37 +++------------ Tests/MarkingTest.php | 54 ++------------------- Tests/WorkflowBuilderTrait.php | 39 ---------------- Tests/WorkflowTest.php | 85 +++++----------------------------- 4 files changed, 23 insertions(+), 192 deletions(-) diff --git a/Marking.php b/Marking.php index 55abef2..4396c2f 100644 --- a/Marking.php +++ b/Marking.php @@ -19,7 +19,7 @@ class Marking { private $places = []; - private $context; + private $context = null; /** * @param int[] $representation Keys are the place name and values should be 1 @@ -27,46 +27,23 @@ class Marking public function __construct(array $representation = []) { foreach ($representation as $place => $nbToken) { - $this->mark($place, $nbToken); + $this->mark($place); } } - public function mark(string $place, int $nbToken = 1) + public function mark(string $place) { - if ($nbToken < 1) { - throw new \LogicException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); - } - - if (!\array_key_exists($place, $this->places)) { - $this->places[$place] = 0; - } - $this->places[$place] += $nbToken; + $this->places[$place] = 1; } - public function unmark(string $place, int $nbToken = 1) + public function unmark(string $place) { - if ($nbToken < 1) { - throw new \LogicException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); - } - - if (!$this->has($place)) { - throw new \LogicException(sprintf('The place "%s" is not marked.', $place)); - } - - $this->places[$place] -= $nbToken; - - if (0 > $this->places[$place]) { - throw new \LogicException(sprintf('The place "%s" could not contain a negative token number.', $place)); - } - - if (0 === $this->places[$place]) { - unset($this->places[$place]); - } + unset($this->places[$place]); } public function has(string $place) { - return \array_key_exists($place, $this->places); + return isset($this->places[$place]); } public function getPlaces() diff --git a/Tests/MarkingTest.php b/Tests/MarkingTest.php index fc06b72..0a1c22b 100644 --- a/Tests/MarkingTest.php +++ b/Tests/MarkingTest.php @@ -22,70 +22,24 @@ public function testMarking() $this->assertTrue($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertPlaces(['a' => 1], $marking); + $this->assertSame(['a' => 1], $marking->getPlaces()); $marking->mark('b'); $this->assertTrue($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertPlaces(['a' => 1, 'b' => 1], $marking); + $this->assertSame(['a' => 1, 'b' => 1], $marking->getPlaces()); $marking->unmark('a'); $this->assertFalse($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertPlaces(['b' => 1], $marking); + $this->assertSame(['b' => 1], $marking->getPlaces()); $marking->unmark('b'); $this->assertFalse($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertPlaces([], $marking); - - $marking->mark('a'); - $this->assertPlaces(['a' => 1], $marking); - - $marking->mark('a'); - $this->assertPlaces(['a' => 2], $marking); - - $marking->unmark('a'); - $this->assertPlaces(['a' => 1], $marking); - - $marking->unmark('a'); - $this->assertPlaces([], $marking); - } - - public function testGuardNotMarked() - { - $marking = new Marking([]); - - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The place "a" is not marked.'); - $marking->unmark('a'); - } - - public function testGuardNotNbTokenLowerThanZero() - { - $marking = new Marking(['a' => 1]); - - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The place "a" could not contain a negative token number.'); - $marking->unmark('a', 2); - } - - public function testGuardNotNbTokenEquals0() - { - $marking = new Marking(['a' => 1]); - - $this->expectException(\LogicException::class); - $this->expectExceptionMessage('The number of tokens must be greater than 0, "0" given.'); - $marking->unmark('a', 0); - } - - private function assertPlaces(array $expected, Marking $marking) - { - $places = $marking->getPlaces(); - ksort($places); - $this->assertSame($expected, $places); + $this->assertSame([], $marking->getPlaces()); } } diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index 5e825a4..ae81232 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -158,43 +158,4 @@ private static function createComplexStateMachineDefinition() // | d | -------------+ // +-----+ } - - private static function createWorkflowWithSameNameBackTransition(): Definition - { - $places = range('a', 'c'); - - $transitions = []; - $transitions[] = new Transition('a_to_bc', 'a', ['b', 'c']); - $transitions[] = new Transition('back1', 'b', 'a'); - $transitions[] = new Transition('back1', 'c', 'b'); - $transitions[] = new Transition('back2', 'c', 'b'); - $transitions[] = new Transition('back2', 'b', 'a'); - $transitions[] = new Transition('c_to_cb', 'c', ['b', 'c']); - - return new Definition($places, $transitions); - - // The graph looks like: - // +-----------------------------------------------------------------+ - // | | - // | | - // | +---------------------------------------------+ | - // v | v | - // +---+ +---------+ +-------+ +---------+ +---+ +-------+ - // | a | --> | a_to_bc | --> | | --> | back2 | --> | | --> | back2 | - // +---+ +---------+ | | +---------+ | | +-------+ - // ^ | | | | - // | | c | <-----+ | b | - // | | | | | | - // | | | +---------+ | | +-------+ - // | | | --> | c_to_cb | --> | | --> | back1 | - // | +-------+ +---------+ +---+ +-------+ - // | | ^ | - // | | | | - // | v | | - // | +-------+ | | - // | | back1 | ----------------------+ | - // | +-------+ | - // | | - // +-----------------------------------------------------------------+ - } } diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 0ed3602..a109655 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -330,32 +330,28 @@ public function testApplyWithSameNameTransition() $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertPlaces([ - 'b' => 1, - 'c' => 1, - ], $marking); + $this->assertFalse($marking->has('a')); + $this->assertTrue($marking->has('b')); + $this->assertTrue($marking->has('c')); $marking = $workflow->apply($subject, 'to_a'); - // Two tokens in "a" - $this->assertPlaces([ - 'a' => 2, - ], $marking); + $this->assertTrue($marking->has('a')); + $this->assertFalse($marking->has('b')); + $this->assertFalse($marking->has('c')); $workflow->apply($subject, 'a_to_bc'); $marking = $workflow->apply($subject, 'b_to_c'); - $this->assertPlaces([ - 'a' => 1, - 'c' => 2, - ], $marking); + $this->assertFalse($marking->has('a')); + $this->assertFalse($marking->has('b')); + $this->assertTrue($marking->has('c')); $marking = $workflow->apply($subject, 'to_a'); - $this->assertPlaces([ - 'a' => 2, - 'c' => 1, - ], $marking); + $this->assertTrue($marking->has('a')); + $this->assertFalse($marking->has('b')); + $this->assertFalse($marking->has('c')); } public function testApplyWithSameNameTransition2() @@ -789,63 +785,6 @@ public function testGetEnabledTransitionsWithSameNameTransition() $this->assertSame('to_a', $transitions[1]->getName()); $this->assertSame('to_a', $transitions[2]->getName()); } - - /** - * @@testWith ["back1"] - * ["back2"] - */ - public function testApplyWithSameNameBackTransition(string $transition) - { - $definition = $this->createWorkflowWithSameNameBackTransition(); - $workflow = new Workflow($definition, new MethodMarkingStore()); - - $subject = new Subject(); - - $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertPlaces([ - 'b' => 1, - 'c' => 1, - ], $marking); - - $marking = $workflow->apply($subject, $transition); - $this->assertPlaces([ - 'a' => 1, - 'b' => 1, - ], $marking); - - $marking = $workflow->apply($subject, $transition); - $this->assertPlaces([ - 'a' => 2, - ], $marking); - - $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertPlaces([ - 'a' => 1, - 'b' => 1, - 'c' => 1, - ], $marking); - - $marking = $workflow->apply($subject, 'c_to_cb'); - $this->assertPlaces([ - 'a' => 1, - 'b' => 2, - 'c' => 1, - ], $marking); - - $marking = $workflow->apply($subject, 'c_to_cb'); - $this->assertPlaces([ - 'a' => 1, - 'b' => 3, - 'c' => 1, - ], $marking); - } - - private function assertPlaces(array $expected, Marking $marking) - { - $places = $marking->getPlaces(); - ksort($places); - $this->assertSame($expected, $places); - } } class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface From 67c6ce942dcddd355bd742a9a023e26c704df0e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 13 Mar 2024 16:04:20 +0100 Subject: [PATCH 121/145] [Workflow] Add support for workflows that need to store many tokens in the marking --- CHANGELOG.md | 1 + Marking.php | 49 +++++++++++++++++--- Tests/MarkingTest.php | 54 +++++++++++++++++++-- Tests/WorkflowBuilderTrait.php | 39 ++++++++++++++++ Tests/WorkflowTest.php | 85 +++++++++++++++++++++++++++++----- 5 files changed, 206 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 247d5e4..6a8430c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ CHANGELOG * Add method `getEnabledTransition()` to `WorkflowInterface` * Automatically register places from transitions + * Add support for workflows that need to store many tokens in the marking 7.0 --- diff --git a/Marking.php b/Marking.php index de9c8a8..58f5ec6 100644 --- a/Marking.php +++ b/Marking.php @@ -22,23 +22,60 @@ class Marking private ?array $context = null; /** - * @param int[] $representation Keys are the place name and values should be 1 + * @param int[] $representation Keys are the place name and values should be superior or equals to 1 */ public function __construct(array $representation = []) { foreach ($representation as $place => $nbToken) { - $this->mark($place); + $this->mark($place, $nbToken); } } - public function mark(string $place): void + /** + * @param int $nbToken + * + * @psalm-param int<1, max> $nbToken + */ + public function mark(string $place /* , int $nbToken = 1 */): void { - $this->places[$place] = 1; + $nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1; + + if ($nbToken < 1) { + throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + } + + $this->places[$place] ??= 0; + $this->places[$place] += $nbToken; } - public function unmark(string $place): void + /** + * @param int $nbToken + * + * @psalm-param int<1, max> $nbToken + */ + public function unmark(string $place /* , int $nbToken = 1 */): void { - unset($this->places[$place]); + $nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1; + + if ($nbToken < 1) { + throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + } + + if (!$this->has($place)) { + throw new \InvalidArgumentException(sprintf('The place "%s" is not marked.', $place)); + } + + $tokenCount = $this->places[$place] - $nbToken; + + if (0 > $tokenCount) { + throw new \InvalidArgumentException(sprintf('The place "%s" could not contain a negative token number: "%s" (initial) - "%s" (nbToken) = "%s".', $place, $this->places[$place], $nbToken, $tokenCount)); + } + + if (0 === $tokenCount) { + unset($this->places[$place]); + } else { + $this->places[$place] = $tokenCount; + } } public function has(string $place): bool diff --git a/Tests/MarkingTest.php b/Tests/MarkingTest.php index 0a1c22b..86a306a 100644 --- a/Tests/MarkingTest.php +++ b/Tests/MarkingTest.php @@ -22,24 +22,70 @@ public function testMarking() $this->assertTrue($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertSame(['a' => 1], $marking->getPlaces()); + $this->assertPlaces(['a' => 1], $marking); $marking->mark('b'); $this->assertTrue($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertSame(['a' => 1, 'b' => 1], $marking->getPlaces()); + $this->assertPlaces(['a' => 1, 'b' => 1], $marking); $marking->unmark('a'); $this->assertFalse($marking->has('a')); $this->assertTrue($marking->has('b')); - $this->assertSame(['b' => 1], $marking->getPlaces()); + $this->assertPlaces(['b' => 1], $marking); $marking->unmark('b'); $this->assertFalse($marking->has('a')); $this->assertFalse($marking->has('b')); - $this->assertSame([], $marking->getPlaces()); + $this->assertPlaces([], $marking); + + $marking->mark('a'); + $this->assertPlaces(['a' => 1], $marking); + + $marking->mark('a'); + $this->assertPlaces(['a' => 2], $marking); + + $marking->unmark('a'); + $this->assertPlaces(['a' => 1], $marking); + + $marking->unmark('a'); + $this->assertPlaces([], $marking); + } + + public function testGuardNotMarked() + { + $marking = new Marking([]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The place "a" is not marked.'); + $marking->unmark('a'); + } + + public function testUnmarkGuardResultTokenCountIsNotNegative() + { + $marking = new Marking(['a' => 1]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The place "a" could not contain a negative token number: "1" (initial) - "2" (nbToken) = "-1".'); + $marking->unmark('a', 2); + } + + public function testUnmarkGuardNbTokenIsGreaterThanZero() + { + $marking = new Marking(['a' => 1]); + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('The number of tokens must be greater than 0, "0" given.'); + $marking->unmark('a', 0); + } + + private function assertPlaces(array $expected, Marking $marking) + { + $places = $marking->getPlaces(); + ksort($places); + $this->assertSame($expected, $places); } } diff --git a/Tests/WorkflowBuilderTrait.php b/Tests/WorkflowBuilderTrait.php index 07a589e..86478bb 100644 --- a/Tests/WorkflowBuilderTrait.php +++ b/Tests/WorkflowBuilderTrait.php @@ -158,4 +158,43 @@ private static function createComplexStateMachineDefinition(): Definition // | d | -------------+ // +-----+ } + + private static function createWorkflowWithSameNameBackTransition(): Definition + { + $places = range('a', 'c'); + + $transitions = []; + $transitions[] = new Transition('a_to_bc', 'a', ['b', 'c']); + $transitions[] = new Transition('back1', 'b', 'a'); + $transitions[] = new Transition('back1', 'c', 'b'); + $transitions[] = new Transition('back2', 'c', 'b'); + $transitions[] = new Transition('back2', 'b', 'a'); + $transitions[] = new Transition('c_to_cb', 'c', ['b', 'c']); + + return new Definition($places, $transitions); + + // The graph looks like: + // +-----------------------------------------------------------------+ + // | | + // | | + // | +---------------------------------------------+ | + // v | v | + // +---+ +---------+ +-------+ +---------+ +---+ +-------+ + // | a | --> | a_to_bc | --> | | --> | back2 | --> | | --> | back2 | + // +---+ +---------+ | | +---------+ | | +-------+ + // ^ | | | | + // | | c | <-----+ | b | + // | | | | | | + // | | | +---------+ | | +-------+ + // | | | --> | c_to_cb | --> | | --> | back1 | + // | +-------+ +---------+ +---+ +-------+ + // | | ^ | + // | | | | + // | v | | + // | +-------+ | | + // | | back1 | ----------------------+ | + // | +-------+ | + // | | + // +-----------------------------------------------------------------+ + } } diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 8e112df..83af790 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -319,28 +319,32 @@ public function testApplyWithSameNameTransition() $marking = $workflow->apply($subject, 'a_to_bc'); - $this->assertFalse($marking->has('a')); - $this->assertTrue($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertPlaces([ + 'b' => 1, + 'c' => 1, + ], $marking); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + // Two tokens in "a" + $this->assertPlaces([ + 'a' => 2, + ], $marking); $workflow->apply($subject, 'a_to_bc'); $marking = $workflow->apply($subject, 'b_to_c'); - $this->assertFalse($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertTrue($marking->has('c')); + $this->assertPlaces([ + 'a' => 1, + 'c' => 2, + ], $marking); $marking = $workflow->apply($subject, 'to_a'); - $this->assertTrue($marking->has('a')); - $this->assertFalse($marking->has('b')); - $this->assertFalse($marking->has('c')); + $this->assertPlaces([ + 'a' => 2, + 'c' => 1, + ], $marking); } public function testApplyWithSameNameTransition2() @@ -776,6 +780,63 @@ public function testGetEnabledTransitionsWithSameNameTransition() $this->assertSame('to_a', $transitions[1]->getName()); $this->assertSame('to_a', $transitions[2]->getName()); } + + /** + * @@testWith ["back1"] + * ["back2"] + */ + public function testApplyWithSameNameBackTransition(string $transition) + { + $definition = $this->createWorkflowWithSameNameBackTransition(); + $workflow = new Workflow($definition, new MethodMarkingStore()); + + $subject = new Subject(); + + $marking = $workflow->apply($subject, 'a_to_bc'); + $this->assertPlaces([ + 'b' => 1, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, $transition); + $this->assertPlaces([ + 'a' => 1, + 'b' => 1, + ], $marking); + + $marking = $workflow->apply($subject, $transition); + $this->assertPlaces([ + 'a' => 2, + ], $marking); + + $marking = $workflow->apply($subject, 'a_to_bc'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 1, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, 'c_to_cb'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 2, + 'c' => 1, + ], $marking); + + $marking = $workflow->apply($subject, 'c_to_cb'); + $this->assertPlaces([ + 'a' => 1, + 'b' => 3, + 'c' => 1, + ], $marking); + } + + private function assertPlaces(array $expected, Marking $marking) + { + $places = $marking->getPlaces(); + ksort($places); + $this->assertSame($expected, $places); + } } class EventDispatcherMock implements \Symfony\Contracts\EventDispatcher\EventDispatcherInterface From 8134ea983e93f02cf0675d8fc699cb082552f609 Mon Sep 17 00:00:00 2001 From: Dariusz Ruminski Date: Mon, 18 Mar 2024 20:27:13 +0100 Subject: [PATCH 122/145] chore: CS fixes --- Tests/Debug/TraceableWorkflowTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Debug/TraceableWorkflowTest.php b/Tests/Debug/TraceableWorkflowTest.php index 5bfcee9..3d8e699 100644 --- a/Tests/Debug/TraceableWorkflowTest.php +++ b/Tests/Debug/TraceableWorkflowTest.php @@ -23,7 +23,7 @@ class TraceableWorkflowTest extends TestCase { private MockObject|Workflow $innerWorkflow; - private StopWatch $stopwatch; + private Stopwatch $stopwatch; private TraceableWorkflow $traceableWorkflow; From 3a084f1b80ecd8a273bdd787e04d9c87bafa894b Mon Sep 17 00:00:00 2001 From: Nicolas Rigaud Date: Thu, 14 Mar 2024 16:33:10 +0100 Subject: [PATCH 123/145] [Workflow] Add EventNameTrait to compute event name strings in subscribers --- CHANGELOG.md | 1 + Event/AnnounceEvent.php | 3 ++ Event/CompletedEvent.php | 3 ++ Event/EnterEvent.php | 3 ++ Event/EnteredEvent.php | 3 ++ Event/EventNameTrait.php | 61 +++++++++++++++++++++++++ Event/GuardEvent.php | 4 ++ Event/LeaveEvent.php | 3 ++ Event/TransitionEvent.php | 3 ++ Tests/Event/EventNameTraitTest.php | 73 ++++++++++++++++++++++++++++++ 10 files changed, 157 insertions(+) create mode 100644 Event/EventNameTrait.php create mode 100644 Tests/Event/EventNameTraitTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a8430c..2926da4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ CHANGELOG * Add method `getEnabledTransition()` to `WorkflowInterface` * Automatically register places from transitions * Add support for workflows that need to store many tokens in the marking + * Add method `getName()` in event classes to build event names in subscribers 7.0 --- diff --git a/Event/AnnounceEvent.php b/Event/AnnounceEvent.php index 0c675a4..0bff3dc 100644 --- a/Event/AnnounceEvent.php +++ b/Event/AnnounceEvent.php @@ -17,6 +17,9 @@ final class AnnounceEvent extends Event { + use EventNameTrait { + getNameForTransition as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Event/CompletedEvent.php b/Event/CompletedEvent.php index 63a5e44..885826f 100644 --- a/Event/CompletedEvent.php +++ b/Event/CompletedEvent.php @@ -17,6 +17,9 @@ final class CompletedEvent extends Event { + use EventNameTrait { + getNameForTransition as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Event/EnterEvent.php b/Event/EnterEvent.php index 46213d4..46e1041 100644 --- a/Event/EnterEvent.php +++ b/Event/EnterEvent.php @@ -17,6 +17,9 @@ final class EnterEvent extends Event { + use EventNameTrait { + getNameForPlace as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Event/EnteredEvent.php b/Event/EnteredEvent.php index 17529b8..a71610d 100644 --- a/Event/EnteredEvent.php +++ b/Event/EnteredEvent.php @@ -17,6 +17,9 @@ final class EnteredEvent extends Event { + use EventNameTrait { + getNameForPlace as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Event/EventNameTrait.php b/Event/EventNameTrait.php new file mode 100644 index 0000000..97b804c --- /dev/null +++ b/Event/EventNameTrait.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Event; + +use Symfony\Component\Workflow\Exception\InvalidArgumentException; + +/** + * @author Nicolas Rigaud + * + * @internal + */ +trait EventNameTrait +{ + /** + * Gets the event name for workflow and transition. + * + * @throws InvalidArgumentException If $transitionName is provided without $workflowName + */ + private static function getNameForTransition(?string $workflowName, ?string $transitionName): string + { + return self::computeName($workflowName, $transitionName); + } + + /** + * Gets the event name for workflow and place. + * + * @throws InvalidArgumentException If $placeName is provided without $workflowName + */ + private static function getNameForPlace(?string $workflowName, ?string $placeName): string + { + return self::computeName($workflowName, $placeName); + } + + private static function computeName(?string $workflowName, ?string $transitionOrPlaceName): string + { + $eventName = strtolower(basename(str_replace('\\', '/', static::class), 'Event')); + + if (null === $workflowName) { + if (null !== $transitionOrPlaceName) { + throw new \InvalidArgumentException('Missing workflow name.'); + } + + return sprintf('workflow.%s', $eventName); + } + + if (null === $transitionOrPlaceName) { + return sprintf('workflow.%s.%s', $workflowName, $eventName); + } + + return sprintf('workflow.%s.%s.%s', $workflowName, $eventName, $transitionOrPlaceName); + } +} diff --git a/Event/GuardEvent.php b/Event/GuardEvent.php index 68a57a9..fbbcf22 100644 --- a/Event/GuardEvent.php +++ b/Event/GuardEvent.php @@ -23,6 +23,10 @@ */ final class GuardEvent extends Event { + use EventNameTrait { + getNameForTransition as public getName; + } + private TransitionBlockerList $transitionBlockerList; public function __construct(object $subject, Marking $marking, Transition $transition, ?WorkflowInterface $workflow = null) diff --git a/Event/LeaveEvent.php b/Event/LeaveEvent.php index ca7ff1a..78fd1b6 100644 --- a/Event/LeaveEvent.php +++ b/Event/LeaveEvent.php @@ -17,6 +17,9 @@ final class LeaveEvent extends Event { + use EventNameTrait { + getNameForPlace as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Event/TransitionEvent.php b/Event/TransitionEvent.php index 6bae159..a7a3dd0 100644 --- a/Event/TransitionEvent.php +++ b/Event/TransitionEvent.php @@ -17,6 +17,9 @@ final class TransitionEvent extends Event { + use EventNameTrait { + getNameForTransition as public getName; + } use HasContextTrait; public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null, array $context = []) diff --git a/Tests/Event/EventNameTraitTest.php b/Tests/Event/EventNameTraitTest.php new file mode 100644 index 0000000..3c74523 --- /dev/null +++ b/Tests/Event/EventNameTraitTest.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\Event; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Event\AnnounceEvent; +use Symfony\Component\Workflow\Event\CompletedEvent; +use Symfony\Component\Workflow\Event\EnteredEvent; +use Symfony\Component\Workflow\Event\EnterEvent; +use Symfony\Component\Workflow\Event\GuardEvent; +use Symfony\Component\Workflow\Event\LeaveEvent; +use Symfony\Component\Workflow\Event\TransitionEvent; + +class EventNameTraitTest extends TestCase +{ + /** + * @dataProvider getEvents + * + * @param class-string $class + */ + public function testEventNames(string $class, ?string $workflowName, ?string $transitionOrPlaceName, string $expected) + { + $name = $class::getName($workflowName, $transitionOrPlaceName); + $this->assertEquals($expected, $name); + } + + public static function getEvents(): iterable + { + yield [AnnounceEvent::class, null, null, 'workflow.announce']; + yield [AnnounceEvent::class, 'post', null, 'workflow.post.announce']; + yield [AnnounceEvent::class, 'post', 'publish', 'workflow.post.announce.publish']; + + yield [CompletedEvent::class, null, null, 'workflow.completed']; + yield [CompletedEvent::class, 'post', null, 'workflow.post.completed']; + yield [CompletedEvent::class, 'post', 'publish', 'workflow.post.completed.publish']; + + yield [EnteredEvent::class, null, null, 'workflow.entered']; + yield [EnteredEvent::class, 'post', null, 'workflow.post.entered']; + yield [EnteredEvent::class, 'post', 'published', 'workflow.post.entered.published']; + + yield [EnterEvent::class, null, null, 'workflow.enter']; + yield [EnterEvent::class, 'post', null, 'workflow.post.enter']; + yield [EnterEvent::class, 'post', 'published', 'workflow.post.enter.published']; + + yield [GuardEvent::class, null, null, 'workflow.guard']; + yield [GuardEvent::class, 'post', null, 'workflow.post.guard']; + yield [GuardEvent::class, 'post', 'publish', 'workflow.post.guard.publish']; + + yield [LeaveEvent::class, null, null, 'workflow.leave']; + yield [LeaveEvent::class, 'post', null, 'workflow.post.leave']; + yield [LeaveEvent::class, 'post', 'published', 'workflow.post.leave.published']; + + yield [TransitionEvent::class, null, null, 'workflow.transition']; + yield [TransitionEvent::class, 'post', null, 'workflow.post.transition']; + yield [TransitionEvent::class, 'post', 'publish', 'workflow.post.transition.publish']; + } + + public function testInvalidArgumentExceptionIsThrownIfWorkflowNameIsMissing() + { + $this->expectException(\InvalidArgumentException::class); + + EnterEvent::getName(null, 'place'); + } +} From 9192856fca8cd065677fcd401e473393a4bfccac Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 18 Apr 2024 09:55:03 +0200 Subject: [PATCH 124/145] Auto-close PRs on subtree-splits --- .gitattributes | 3 +- .github/PULL_REQUEST_TEMPLATE.md | 8 +++++ .github/workflows/check-subtree-split.yml | 37 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/check-subtree-split.yml diff --git a/.gitattributes b/.gitattributes index 84c7add..14c3c35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4689c4d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/symfony + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/.github/workflows/check-subtree-split.yml b/.github/workflows/check-subtree-split.yml new file mode 100644 index 0000000..16be48b --- /dev/null +++ b/.github/workflows/check-subtree-split.yml @@ -0,0 +1,37 @@ +name: Check subtree split + +on: + pull_request_target: + +jobs: + close-pull-request: + runs-on: ubuntu-latest + + steps: + - name: Close pull request + uses: actions/github-script@v6 + with: + script: | + if (context.repo.owner === "symfony") { + github.rest.issues.createComment({ + owner: "symfony", + repo: context.repo.repo, + issue_number: context.issue.number, + body: ` + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/symfony + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! + ` + }); + + github.rest.pulls.update({ + owner: "symfony", + repo: context.repo.repo, + pull_number: context.issue.number, + state: "closed" + }); + } From 3f4aa0f1fe40139fd3b816df46c257c2642285e9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 31 May 2024 16:33:22 +0200 Subject: [PATCH 125/145] Revert "minor #54653 Auto-close PRs on subtree-splits (nicolas-grekas)" This reverts commit 2c9352dd91ebaf37b8a3e3c26fd8e1306df2fb73, reversing changes made to 18c3e87f1512be2cc50e90235b144b13bc347258. --- .gitattributes | 3 +- .github/PULL_REQUEST_TEMPLATE.md | 8 ----- .github/workflows/check-subtree-split.yml | 37 ----------------------- 3 files changed, 2 insertions(+), 46 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/workflows/check-subtree-split.yml diff --git a/.gitattributes b/.gitattributes index 14c3c35..84c7add 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,4 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.git* export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 4689c4d..0000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,8 +0,0 @@ -Please do not submit any Pull Requests here. They will be closed. ---- - -Please submit your PR here instead: -https://github.com/symfony/symfony - -This repository is what we call a "subtree split": a read-only subset of that main repository. -We're looking forward to your PR there! diff --git a/.github/workflows/check-subtree-split.yml b/.github/workflows/check-subtree-split.yml deleted file mode 100644 index 16be48b..0000000 --- a/.github/workflows/check-subtree-split.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Check subtree split - -on: - pull_request_target: - -jobs: - close-pull-request: - runs-on: ubuntu-latest - - steps: - - name: Close pull request - uses: actions/github-script@v6 - with: - script: | - if (context.repo.owner === "symfony") { - github.rest.issues.createComment({ - owner: "symfony", - repo: context.repo.repo, - issue_number: context.issue.number, - body: ` - Thanks for your Pull Request! We love contributions. - - However, you should instead open your PR on the main repository: - https://github.com/symfony/symfony - - This repository is what we call a "subtree split": a read-only subset of that main repository. - We're looking forward to your PR there! - ` - }); - - github.rest.pulls.update({ - owner: "symfony", - repo: context.repo.repo, - pull_number: context.issue.number, - state: "closed" - }); - } From 6e7c3ad0a737049a47d834c44158f465c431b692 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 3 Jun 2024 15:27:28 +0200 Subject: [PATCH 126/145] use constructor property promotion --- Dumper/MermaidDumper.php | 12 +++----- Dumper/PlantUmlDumper.php | 8 ++---- Event/Event.php | 17 ++++------- EventListener/AuditTrailListener.php | 8 ++---- EventListener/GuardExpression.php | 11 +++----- EventListener/GuardListener.php | 26 ++++++----------- Exception/NotEnabledTransitionException.php | 13 +++++---- Exception/TransitionException.php | 19 +++++-------- Metadata/InMemoryMetadataStore.php | 11 ++++---- SupportStrategy/InstanceOfSupportStrategy.php | 8 ++---- Transition.php | 9 +++--- TransitionBlocker.php | 14 ++++------ Validator/WorkflowValidator.php | 8 ++---- Workflow.php | 28 ++++++++----------- 14 files changed, 75 insertions(+), 117 deletions(-) diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index ed67c6f..346ff50 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -39,22 +39,18 @@ class MermaidDumper implements DumperInterface self::TRANSITION_TYPE_WORKFLOW, ]; - private string $direction; - private string $transitionType; - /** * Just tracking the transition id is in some cases inaccurate to * get the link's number for styling purposes. */ private int $linkCount = 0; - public function __construct(string $transitionType, string $direction = self::DIRECTION_LEFT_TO_RIGHT) - { + public function __construct( + private string $transitionType, + private string $direction = self::DIRECTION_LEFT_TO_RIGHT, + ) { $this->validateDirection($direction); $this->validateTransitionType($transitionType); - - $this->direction = $direction; - $this->transitionType = $transitionType; } public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 2a232d4..f17d5c3 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -51,14 +51,12 @@ class PlantUmlDumper implements DumperInterface ], ]; - private string $transitionType = self::STATEMACHINE_TRANSITION; - - public function __construct(string $transitionType) - { + public function __construct( + private string $transitionType, + ) { if (!\in_array($transitionType, self::TRANSITION_TYPES, true)) { throw new \InvalidArgumentException("Transition type '$transitionType' does not exist."); } - $this->transitionType = $transitionType; } public function dump(Definition $definition, ?Marking $marking = null, array $options = []): string diff --git a/Event/Event.php b/Event/Event.php index 1efbc79..c3e6a6f 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -23,17 +23,12 @@ */ class Event extends BaseEvent { - private object $subject; - private Marking $marking; - private ?Transition $transition; - private ?WorkflowInterface $workflow; - - public function __construct(object $subject, Marking $marking, ?Transition $transition = null, ?WorkflowInterface $workflow = null) - { - $this->subject = $subject; - $this->marking = $marking; - $this->transition = $transition; - $this->workflow = $workflow; + public function __construct( + private object $subject, + private Marking $marking, + private ?Transition $transition = null, + private ?WorkflowInterface $workflow = null, + ) { } public function getMarking(): Marking diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index b5e2734..15208c6 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -20,11 +20,9 @@ */ class AuditTrailListener implements EventSubscriberInterface { - private LoggerInterface $logger; - - public function __construct(LoggerInterface $logger) - { - $this->logger = $logger; + public function __construct( + private LoggerInterface $logger, + ) { } public function onLeave(Event $event): void diff --git a/EventListener/GuardExpression.php b/EventListener/GuardExpression.php index 27bf8dc..deb148d 100644 --- a/EventListener/GuardExpression.php +++ b/EventListener/GuardExpression.php @@ -15,13 +15,10 @@ class GuardExpression { - private Transition $transition; - private string $expression; - - public function __construct(Transition $transition, string $expression) - { - $this->transition = $transition; - $this->expression = $expression; + public function __construct( + private Transition $transition, + private string $expression, + ) { } public function getTransition(): Transition diff --git a/EventListener/GuardListener.php b/EventListener/GuardListener.php index 6972a89..23cdd7a 100644 --- a/EventListener/GuardListener.php +++ b/EventListener/GuardListener.php @@ -24,23 +24,15 @@ */ class GuardListener { - private array $configuration; - private ExpressionLanguage $expressionLanguage; - private TokenStorageInterface $tokenStorage; - private AuthorizationCheckerInterface $authorizationChecker; - private AuthenticationTrustResolverInterface $trustResolver; - private ?RoleHierarchyInterface $roleHierarchy; - private ?ValidatorInterface $validator; - - public function __construct(array $configuration, ExpressionLanguage $expressionLanguage, TokenStorageInterface $tokenStorage, AuthorizationCheckerInterface $authorizationChecker, AuthenticationTrustResolverInterface $trustResolver, ?RoleHierarchyInterface $roleHierarchy = null, ?ValidatorInterface $validator = null) - { - $this->configuration = $configuration; - $this->expressionLanguage = $expressionLanguage; - $this->tokenStorage = $tokenStorage; - $this->authorizationChecker = $authorizationChecker; - $this->trustResolver = $trustResolver; - $this->roleHierarchy = $roleHierarchy; - $this->validator = $validator; + public function __construct( + private array $configuration, + private ExpressionLanguage $expressionLanguage, + private TokenStorageInterface $tokenStorage, + private AuthorizationCheckerInterface $authorizationChecker, + private AuthenticationTrustResolverInterface $trustResolver, + private ?RoleHierarchyInterface $roleHierarchy = null, + private ?ValidatorInterface $validator = null, + ) { } public function onTransition(GuardEvent $event, string $eventName): void diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index 4144caf..81f2f72 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -21,13 +21,14 @@ */ class NotEnabledTransitionException extends TransitionException { - private TransitionBlockerList $transitionBlockerList; - - public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, TransitionBlockerList $transitionBlockerList, array $context = []) - { + public function __construct( + object $subject, + string $transitionName, + WorkflowInterface $workflow, + private TransitionBlockerList $transitionBlockerList, + array $context = [], + ) { parent::__construct($subject, $transitionName, $workflow, sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); - - $this->transitionBlockerList = $transitionBlockerList; } public function getTransitionBlockerList(): TransitionBlockerList diff --git a/Exception/TransitionException.php b/Exception/TransitionException.php index 0cf1afb..e5c3846 100644 --- a/Exception/TransitionException.php +++ b/Exception/TransitionException.php @@ -19,19 +19,14 @@ */ class TransitionException extends LogicException { - private object $subject; - private string $transitionName; - private WorkflowInterface $workflow; - private array $context; - - public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, string $message, array $context = []) - { + public function __construct( + private object $subject, + private string $transitionName, + private WorkflowInterface $workflow, + string $message, + private array $context = [], + ) { parent::__construct($message); - - $this->subject = $subject; - $this->transitionName = $transitionName; - $this->workflow = $workflow; - $this->context = $context; } public function getSubject(): object diff --git a/Metadata/InMemoryMetadataStore.php b/Metadata/InMemoryMetadataStore.php index d13f956..b88514b 100644 --- a/Metadata/InMemoryMetadataStore.php +++ b/Metadata/InMemoryMetadataStore.php @@ -20,17 +20,16 @@ final class InMemoryMetadataStore implements MetadataStoreInterface { use GetMetadataTrait; - private array $workflowMetadata; - private array $placesMetadata; private \SplObjectStorage $transitionsMetadata; /** * @param \SplObjectStorage|null $transitionsMetadata */ - public function __construct(array $workflowMetadata = [], array $placesMetadata = [], ?\SplObjectStorage $transitionsMetadata = null) - { - $this->workflowMetadata = $workflowMetadata; - $this->placesMetadata = $placesMetadata; + public function __construct( + private array $workflowMetadata = [], + private array $placesMetadata = [], + ?\SplObjectStorage $transitionsMetadata = null, + ) { $this->transitionsMetadata = $transitionsMetadata ?? new \SplObjectStorage(); } diff --git a/SupportStrategy/InstanceOfSupportStrategy.php b/SupportStrategy/InstanceOfSupportStrategy.php index 86bd107..8d8a4b5 100644 --- a/SupportStrategy/InstanceOfSupportStrategy.php +++ b/SupportStrategy/InstanceOfSupportStrategy.php @@ -19,11 +19,9 @@ */ final class InstanceOfSupportStrategy implements WorkflowSupportStrategyInterface { - private string $className; - - public function __construct(string $className) - { - $this->className = $className; + public function __construct( + private string $className, + ) { } public function supports(WorkflowInterface $workflow, object $subject): bool diff --git a/Transition.php b/Transition.php index 50d834b..05fe267 100644 --- a/Transition.php +++ b/Transition.php @@ -17,7 +17,6 @@ */ class Transition { - private string $name; private array $froms; private array $tos; @@ -25,9 +24,11 @@ class Transition * @param string|string[] $froms * @param string|string[] $tos */ - public function __construct(string $name, string|array $froms, string|array $tos) - { - $this->name = $name; + public function __construct( + private string $name, + string|array $froms, + string|array $tos, + ) { $this->froms = (array) $froms; $this->tos = (array) $tos; } diff --git a/TransitionBlocker.php b/TransitionBlocker.php index 4864598..6a745a2 100644 --- a/TransitionBlocker.php +++ b/TransitionBlocker.php @@ -20,21 +20,17 @@ final class TransitionBlocker public const BLOCKED_BY_EXPRESSION_GUARD_LISTENER = '326a1e9c-0c12-11e8-ba89-0ed5f89f718b'; public const UNKNOWN = 'e8b5bbb9-5913-4b98-bfa6-65dbd228a82a'; - private string $message; - private string $code; - private array $parameters; - /** * @param string $code Code is a machine-readable string, usually an UUID * @param array $parameters This is useful if you would like to pass around the condition values, that * blocked the transition. E.g. for a condition "distance must be larger than * 5 miles", you might want to pass around the value of 5. */ - public function __construct(string $message, string $code, array $parameters = []) - { - $this->message = $message; - $this->code = $code; - $this->parameters = $parameters; + public function __construct( + private string $message, + private string $code, + private array $parameters = [], + ) { } /** diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index 2afefb7..f9bba8c 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -20,11 +20,9 @@ */ class WorkflowValidator implements DefinitionValidatorInterface { - private bool $singlePlace; - - public function __construct(bool $singlePlace = false) - { - $this->singlePlace = $singlePlace; + public function __construct( + private bool $singlePlace = false, + ) { } public function validate(Definition $definition, string $name): void diff --git a/Workflow.php b/Workflow.php index cfad75f..2a61d48 100644 --- a/Workflow.php +++ b/Workflow.php @@ -52,28 +52,22 @@ class Workflow implements WorkflowInterface WorkflowEvents::ANNOUNCE => self::DISABLE_ANNOUNCE_EVENT, ]; - private Definition $definition; private MarkingStoreInterface $markingStore; - private ?EventDispatcherInterface $dispatcher; - private string $name; /** - * When `null` fire all events (the default behaviour). - * Setting this to an empty array `[]` means no events are dispatched (except the {@see GuardEvent}). - * Passing an array with WorkflowEvents will allow only those events to be dispatched plus - * the {@see GuardEvent}. - * - * @var array|string[]|null + * @param array|string[]|null $eventsToDispatch When `null` fire all events (the default behaviour). + * Setting this to an empty array `[]` means no events are dispatched (except the {@see GuardEvent}). + * Passing an array with WorkflowEvents will allow only those events to be dispatched plus + * the {@see GuardEvent}. */ - private ?array $eventsToDispatch = null; - - public function __construct(Definition $definition, ?MarkingStoreInterface $markingStore = null, ?EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', ?array $eventsToDispatch = null) - { - $this->definition = $definition; + public function __construct( + private Definition $definition, + ?MarkingStoreInterface $markingStore = null, + private ?EventDispatcherInterface $dispatcher = null, + private string $name = 'unnamed', + private ?array $eventsToDispatch = null, + ) { $this->markingStore = $markingStore ?? new MethodMarkingStore(); - $this->dispatcher = $dispatcher; - $this->name = $name; - $this->eventsToDispatch = $eventsToDispatch; } public function getMarking(object $subject, array $context = []): Marking From f642a9cf9b954712d95a220ca48c62bacde7f073 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Thu, 20 Jun 2024 17:52:34 +0200 Subject: [PATCH 127/145] Prefix all sprintf() calls --- Attribute/BuildEventNameTrait.php | 8 ++--- DataCollector/WorkflowDataCollector.php | 16 +++++----- Definition.php | 2 +- .../WorkflowGuardListenerPass.php | 2 +- Dumper/GraphvizDumper.php | 32 +++++++++---------- Dumper/MermaidDumper.php | 22 ++++++------- Dumper/PlantUmlDumper.php | 6 ++-- Dumper/StateMachineGraphvizDumper.php | 2 +- Event/EventNameTrait.php | 6 ++-- EventListener/AuditTrailListener.php | 6 ++-- EventListener/ExpressionLanguage.php | 4 +-- Exception/NotEnabledTransitionException.php | 2 +- Exception/UndefinedTransitionException.php | 2 +- Marking.php | 8 ++--- MarkingStore/MethodMarkingStore.php | 6 ++-- Registry.php | 4 +-- Tests/Attribute/AsListenerTest.php | 4 +-- Tests/StateMachineTest.php | 4 +-- Validator/StateMachineValidator.php | 8 ++--- Validator/WorkflowValidator.php | 6 ++-- Workflow.php | 32 +++++++++---------- 21 files changed, 91 insertions(+), 91 deletions(-) diff --git a/Attribute/BuildEventNameTrait.php b/Attribute/BuildEventNameTrait.php index 93eeee7..d6d3c26 100644 --- a/Attribute/BuildEventNameTrait.php +++ b/Attribute/BuildEventNameTrait.php @@ -24,16 +24,16 @@ private static function buildEventName(string $keyword, string $argument, ?strin { if (null === $workflow) { if (null !== $node) { - throw new LogicException(sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class)); + throw new LogicException(\sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class)); } - return sprintf('workflow.%s', $keyword); + return \sprintf('workflow.%s', $keyword); } if (null === $node) { - return sprintf('workflow.%s.%s', $workflow, $keyword); + return \sprintf('workflow.%s.%s', $workflow, $keyword); } - return sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node); + return \sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node); } } diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index ef09b98..ee923a1 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -94,8 +94,8 @@ protected function getCasters(): array ...parent::getCasters(), TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) { unset( - $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], - $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], + $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], + $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], ); $s->cut += 2; @@ -129,9 +129,9 @@ private function getEventListeners(WorkflowInterface $workflow): array 'entered', ]; foreach ($subEventNames as $subEventName) { - $eventNames[] = sprintf('workflow.%s', $subEventName); - $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); - $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $place); + $eventNames[] = \sprintf('workflow.%s', $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $place); } foreach ($eventNames as $eventName) { foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { @@ -151,9 +151,9 @@ private function getEventListeners(WorkflowInterface $workflow): array 'announce', ]; foreach ($subEventNames as $subEventName) { - $eventNames[] = sprintf('workflow.%s', $subEventName); - $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); - $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $transition->getName()); + $eventNames[] = \sprintf('workflow.%s', $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $transition->getName()); } foreach ($eventNames as $eventName) { foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { diff --git a/Definition.php b/Definition.php index 96ec1fe..0b5697b 100644 --- a/Definition.php +++ b/Definition.php @@ -86,7 +86,7 @@ private function setInitialPlaces(string|array|null $places): void foreach ($places as $place) { if (!isset($this->places[$place])) { - throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', $place)); + throw new LogicException(\sprintf('Place "%s" cannot be the initial place as it does not exist.', $place)); } } diff --git a/DependencyInjection/WorkflowGuardListenerPass.php b/DependencyInjection/WorkflowGuardListenerPass.php index ba81a7b..ccf00f0 100644 --- a/DependencyInjection/WorkflowGuardListenerPass.php +++ b/DependencyInjection/WorkflowGuardListenerPass.php @@ -38,7 +38,7 @@ public function process(ContainerBuilder $container): void foreach ($servicesNeeded as $service) { if (!$container->has($service)) { - throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); + throw new LogicException(\sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); } } } diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 7e6ffa4..2aaf549 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -154,14 +154,14 @@ protected function addPlaces(array $places, float $withMetadata): string } if ($withMetadata) { - $escapedLabel = sprintf('<%s%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); + $escapedLabel = \sprintf('<%s%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); // Don't include metadata in default attributes used to format the place unset($place['attributes']['metadata']); } else { - $escapedLabel = sprintf('"%s"', $this->escape($placeName)); + $escapedLabel = \sprintf('"%s"', $this->escape($placeName)); } - $code .= sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); + $code .= \sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -176,12 +176,12 @@ protected function addTransitions(array $transitions, bool $withMetadata): strin foreach ($transitions as $i => $place) { if ($withMetadata) { - $escapedLabel = sprintf('<%s%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); + $escapedLabel = \sprintf('<%s%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); } else { $escapedLabel = '"'.$this->escape($place['name']).'"'; } - $code .= sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); + $code .= \sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -229,12 +229,12 @@ protected function addEdges(array $edges): string foreach ($edges as $edge) { if ('from' === $edge['direction']) { - $code .= sprintf(" place_%s -> transition_%s [style=\"solid\"];\n", + $code .= \sprintf(" place_%s -> transition_%s [style=\"solid\"];\n", $this->dotize($edge['from']), $this->dotize($edge['transition_number']) ); } else { - $code .= sprintf(" transition_%s -> place_%s [style=\"solid\"];\n", + $code .= \sprintf(" transition_%s -> place_%s [style=\"solid\"];\n", $this->dotize($edge['transition_number']), $this->dotize($edge['to']) ); @@ -249,9 +249,9 @@ protected function addEdges(array $edges): string */ protected function startDot(array $options, string $label): string { - return sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", + return \sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", $this->addOptions($options['graph']), - '""' !== $label && '<>' !== $label ? sprintf(' label=%s', $label) : '', + '""' !== $label && '<>' !== $label ? \sprintf(' label=%s', $label) : '', $this->addOptions($options['node']), $this->addOptions($options['edge']) ); @@ -289,7 +289,7 @@ protected function addAttributes(array $attributes): string $code = []; foreach ($attributes as $k => $v) { - $code[] = sprintf('%s="%s"', $k, $this->escape($v)); + $code[] = \sprintf('%s="%s"', $k, $this->escape($v)); } return $code ? ' '.implode(' ', $code) : ''; @@ -309,17 +309,17 @@ protected function formatLabel(Definition $definition, string $withMetadata, arr if (!$withMetadata) { // Only currentLabel to handle. If null, will be translated to empty string - return sprintf('"%s"', $this->escape($currentLabel)); + return \sprintf('"%s"', $this->escape($currentLabel)); } $workflowMetadata = $definition->getMetadataStore()->getWorkflowMetadata(); if ('' === $currentLabel) { // Only metadata to handle - return sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); + return \sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); } // currentLabel and metadata to handle - return sprintf('<%s%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); + return \sprintf('<%s%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); } private function addOptions(array $options): string @@ -327,7 +327,7 @@ private function addOptions(array $options): string $code = []; foreach ($options as $k => $v) { - $code[] = sprintf('%s="%s"', $k, $v); + $code[] = \sprintf('%s="%s"', $k, $v); } return implode(' ', $code); @@ -344,10 +344,10 @@ private function addMetadata(array $metadata, bool $lineBreakFirstIfNotEmpty = t foreach ($metadata as $key => $value) { if ($skipSeparator) { - $code[] = sprintf('%s: %s', $this->escape($key), $this->escape($value)); + $code[] = \sprintf('%s: %s', $this->escape($key), $this->escape($value)); $skipSeparator = false; } else { - $code[] = sprintf('%s%s: %s', '
', $this->escape($key), $this->escape($value)); + $code[] = \sprintf('%s%s: %s', '
', $this->escape($key), $this->escape($value)); } } diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index 346ff50..bd7a6fa 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -138,7 +138,7 @@ private function preparePlace(int $placeId, string $placeName, array $meta, bool $placeNodeName = 'place'.$placeId; $placeNodeFormat = '%s'.$labelShape; - $placeNode = sprintf($placeNodeFormat, $placeNodeName, $placeLabel); + $placeNode = \sprintf($placeNodeFormat, $placeNodeName, $placeLabel); $placeStyle = $this->styleNode($meta, $placeNodeName, $hasMarking); @@ -150,7 +150,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal $nodeStyles = []; if (\array_key_exists('bg_color', $meta)) { - $nodeStyles[] = sprintf( + $nodeStyles[] = \sprintf( 'fill:%s', $meta['bg_color'] ); @@ -164,7 +164,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal return ''; } - return sprintf('style %s %s', $nodeName, implode(',', $nodeStyles)); + return \sprintf('style %s %s', $nodeName, implode(',', $nodeStyles)); } /** @@ -175,26 +175,26 @@ private function escape(string $label): string { $label = str_replace('"', '#quot;', $label); - return sprintf('"%s"', $label); + return \sprintf('"%s"', $label); } public function validateDirection(string $direction): void { if (!\in_array($direction, self::VALID_DIRECTIONS, true)) { - throw new InvalidArgumentException(sprintf('Direction "%s" is not valid, valid directions are: "%s".', $direction, implode(', ', self::VALID_DIRECTIONS))); + throw new InvalidArgumentException(\sprintf('Direction "%s" is not valid, valid directions are: "%s".', $direction, implode(', ', self::VALID_DIRECTIONS))); } } private function validateTransitionType(string $transitionType): void { if (!\in_array($transitionType, self::VALID_TRANSITION_TYPES, true)) { - throw new InvalidArgumentException(sprintf('Transition type "%s" is not valid, valid types are: "%s".', $transitionType, implode(', ', self::VALID_TRANSITION_TYPES))); + throw new InvalidArgumentException(\sprintf('Transition type "%s" is not valid, valid types are: "%s".', $transitionType, implode(', ', self::VALID_TRANSITION_TYPES))); } } private function styleStateMachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array { - $transitionOutput = [sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; + $transitionOutput = [\sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -213,7 +213,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti $transitionLabel = $this->escape($transitionLabel); $transitionNodeName = 'transition'.$transitionId; - $transitionOutput[] = sprintf('%s[%s]', $transitionNodeName, $transitionLabel); + $transitionOutput[] = \sprintf('%s[%s]', $transitionNodeName, $transitionLabel); $transitionNodeStyle = $this->styleNode($transitionMeta, $transitionNodeName); if ('' !== $transitionNodeStyle) { @@ -221,7 +221,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti } $connectionStyle = '%s-->%s'; - $transitionOutput[] = sprintf($connectionStyle, $from, $transitionNodeName); + $transitionOutput[] = \sprintf($connectionStyle, $from, $transitionNodeName); $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -230,7 +230,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti ++$this->linkCount; - $transitionOutput[] = sprintf($connectionStyle, $transitionNodeName, $to); + $transitionOutput[] = \sprintf($connectionStyle, $transitionNodeName, $to); $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -245,7 +245,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti private function styleLink(array $transitionMeta): string { if (\array_key_exists('color', $transitionMeta)) { - return sprintf('linkStyle %d stroke:%s', $this->linkCount, $transitionMeta['color']); + return \sprintf('linkStyle %d stroke:%s', $this->linkCount, $transitionMeta['color']); } return ''; diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index f17d5c3..6943dca 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -226,9 +226,9 @@ private function getTransitionEscapedWithStyle(MetadataStoreInterface $workflowM if (null !== $color) { // Close and open before and after every '\n' string, // so that the style is applied properly on every line - $to = str_replace('\n', sprintf('\n', $color), $to); + $to = str_replace('\n', \sprintf('\n', $color), $to); - $to = sprintf( + $to = \sprintf( '%2$s', $color, $to @@ -245,7 +245,7 @@ private function getTransitionColor(string $color): string $color = '#'.$color; } - return sprintf('[%s]', $color); + return \sprintf('[%s]', $color); } private function getColorId(string $color): string diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index e054cb4..7bd9d73 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -89,7 +89,7 @@ protected function addEdges(array $edges): string foreach ($edges as $id => $edges) { foreach ($edges as $edge) { - $code .= sprintf( + $code .= \sprintf( " place_%s -> place_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), diff --git a/Event/EventNameTrait.php b/Event/EventNameTrait.php index 97b804c..1f77b37 100644 --- a/Event/EventNameTrait.php +++ b/Event/EventNameTrait.php @@ -49,13 +49,13 @@ private static function computeName(?string $workflowName, ?string $transitionOr throw new \InvalidArgumentException('Missing workflow name.'); } - return sprintf('workflow.%s', $eventName); + return \sprintf('workflow.%s', $eventName); } if (null === $transitionOrPlaceName) { - return sprintf('workflow.%s.%s', $workflowName, $eventName); + return \sprintf('workflow.%s.%s', $workflowName, $eventName); } - return sprintf('workflow.%s.%s.%s', $workflowName, $eventName, $transitionOrPlaceName); + return \sprintf('workflow.%s.%s.%s', $workflowName, $eventName, $transitionOrPlaceName); } } diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 15208c6..fe7ccdf 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -28,19 +28,19 @@ public function __construct( public function onLeave(Event $event): void { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } public function onTransition(Event $event): void { - $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } public function onEnter(Event $event): void { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index a70e5f7..257f885 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -26,9 +26,9 @@ protected function registerFunctions(): void { parent::registerFunctions(); - $this->register('is_granted', fn ($attributes, $object = 'null') => sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); + $this->register('is_granted', fn ($attributes, $object = 'null') => \sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); - $this->register('is_valid', fn ($object = 'null', $groups = 'null') => sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { + $this->register('is_valid', fn ($object = 'null', $groups = 'null') => \sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed. Try running "composer require symfony/validator".'); } diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index 81f2f72..b80693b 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -28,7 +28,7 @@ public function __construct( private TransitionBlockerList $transitionBlockerList, array $context = [], ) { - parent::__construct($subject, $transitionName, $workflow, sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); + parent::__construct($subject, $transitionName, $workflow, \sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); } public function getTransitionBlockerList(): TransitionBlockerList diff --git a/Exception/UndefinedTransitionException.php b/Exception/UndefinedTransitionException.php index 75d3848..5a8ecf8 100644 --- a/Exception/UndefinedTransitionException.php +++ b/Exception/UndefinedTransitionException.php @@ -22,6 +22,6 @@ class UndefinedTransitionException extends TransitionException { public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, array $context = []) { - parent::__construct($subject, $transitionName, $workflow, sprintf('Transition "%s" is not defined for workflow "%s".', $transitionName, $workflow->getName()), $context); + parent::__construct($subject, $transitionName, $workflow, \sprintf('Transition "%s" is not defined for workflow "%s".', $transitionName, $workflow->getName()), $context); } } diff --git a/Marking.php b/Marking.php index 58f5ec6..c3629a2 100644 --- a/Marking.php +++ b/Marking.php @@ -41,7 +41,7 @@ public function mark(string $place /* , int $nbToken = 1 */): void $nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1; if ($nbToken < 1) { - throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + throw new \InvalidArgumentException(\sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); } $this->places[$place] ??= 0; @@ -58,17 +58,17 @@ public function unmark(string $place /* , int $nbToken = 1 */): void $nbToken = 1 < \func_num_args() ? func_get_arg(1) : 1; if ($nbToken < 1) { - throw new \InvalidArgumentException(sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); + throw new \InvalidArgumentException(\sprintf('The number of tokens must be greater than 0, "%s" given.', $nbToken)); } if (!$this->has($place)) { - throw new \InvalidArgumentException(sprintf('The place "%s" is not marked.', $place)); + throw new \InvalidArgumentException(\sprintf('The place "%s" is not marked.', $place)); } $tokenCount = $this->places[$place] - $nbToken; if (0 > $tokenCount) { - throw new \InvalidArgumentException(sprintf('The place "%s" could not contain a negative token number: "%s" (initial) - "%s" (nbToken) = "%s".', $place, $this->places[$place], $nbToken, $tokenCount)); + throw new \InvalidArgumentException(\sprintf('The place "%s" could not contain a negative token number: "%s" (initial) - "%s" (nbToken) = "%s".', $place, $this->places[$place], $nbToken, $tokenCount)); } if (0 === $tokenCount) { diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index 773328f..50d6169 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -53,7 +53,7 @@ public function getMarking(object $subject): Marking try { $marking = ($this->getGetter($subject))(); } catch (\Error $e) { - $unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); + $unInitializedPropertyMessage = \sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); if ($e->getMessage() !== $unInitializedPropertyMessage) { throw $e; } @@ -66,7 +66,7 @@ public function getMarking(object $subject): Marking if ($this->singleState) { $marking = [(string) $marking => 1]; } elseif (!\is_array($marking)) { - throw new LogicException(sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property)); + throw new LogicException(\sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property)); } return new Marking($marking); @@ -118,7 +118,7 @@ private static function getType(object $subject, string $property, string $metho } catch (\ReflectionException) { } - throw new LogicException(sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property)); + throw new LogicException(\sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property)); } } diff --git a/Registry.php b/Registry.php index 787bc21..08017a3 100644 --- a/Registry.php +++ b/Registry.php @@ -49,13 +49,13 @@ public function get(object $subject, ?string $workflowName = null): WorkflowInte } if (!$matched) { - throw new InvalidArgumentException(sprintf('Unable to find a workflow for class "%s".', get_debug_type($subject))); + throw new InvalidArgumentException(\sprintf('Unable to find a workflow for class "%s".', get_debug_type($subject))); } if (2 <= \count($matched)) { $names = array_map(static fn (WorkflowInterface $workflow): string => $workflow->getName(), $matched); - throw new InvalidArgumentException(sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); + throw new InvalidArgumentException(\sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); } return $matched[0]; diff --git a/Tests/Attribute/AsListenerTest.php b/Tests/Attribute/AsListenerTest.php index a858626..0a8c232 100644 --- a/Tests/Attribute/AsListenerTest.php +++ b/Tests/Attribute/AsListenerTest.php @@ -64,7 +64,7 @@ public static function provideOkTests(): iterable public function testTransitionThrowException(string $class) { $this->expectException(LogicException::class); - $this->expectExceptionMessage(sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class)); + $this->expectExceptionMessage(\sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class)); new $class(transition: 'some'); } @@ -83,7 +83,7 @@ public static function provideTransitionThrowException(): iterable public function testPlaceThrowException(string $class) { $this->expectException(LogicException::class); - $this->expectExceptionMessage(sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class)); + $this->expectExceptionMessage(\sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class)); new $class(place: 'some'); } diff --git a/Tests/StateMachineTest.php b/Tests/StateMachineTest.php index e991707..5d10fde 100644 --- a/Tests/StateMachineTest.php +++ b/Tests/StateMachineTest.php @@ -88,7 +88,7 @@ public function testBuildTransitionBlockerListReturnsExpectedReasonOnBranchMerge $net = new StateMachine($definition, null, $dispatcher); $dispatcher->addListener('workflow.guard', function (GuardEvent $event) { - $event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); + $event->addTransitionBlocker(new TransitionBlocker(\sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); }); $subject = new Subject(); @@ -124,7 +124,7 @@ public function testApplyReturnsExpectedReasonOnBranchMerge() $net = new StateMachine($definition, null, $dispatcher); $dispatcher->addListener('workflow.guard', function (GuardEvent $event) { - $event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); + $event->addTransitionBlocker(new TransitionBlocker(\sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); }); $subject = new Subject(); diff --git a/Validator/StateMachineValidator.php b/Validator/StateMachineValidator.php index 521fc88..626a20e 100644 --- a/Validator/StateMachineValidator.php +++ b/Validator/StateMachineValidator.php @@ -25,19 +25,19 @@ public function validate(Definition $definition, string $name): void foreach ($definition->getTransitions() as $transition) { // Make sure that each transition has exactly one TO if (1 !== \count($transition->getTos())) { - throw new InvalidDefinitionException(sprintf('A transition in StateMachine can only have one output. But the transition "%s" in StateMachine "%s" has %d outputs.', $transition->getName(), $name, \count($transition->getTos()))); + throw new InvalidDefinitionException(\sprintf('A transition in StateMachine can only have one output. But the transition "%s" in StateMachine "%s" has %d outputs.', $transition->getName(), $name, \count($transition->getTos()))); } // Make sure that each transition has exactly one FROM $froms = $transition->getFroms(); if (1 !== \count($froms)) { - throw new InvalidDefinitionException(sprintf('A transition in StateMachine can only have one input. But the transition "%s" in StateMachine "%s" has %d inputs.', $transition->getName(), $name, \count($froms))); + throw new InvalidDefinitionException(\sprintf('A transition in StateMachine can only have one input. But the transition "%s" in StateMachine "%s" has %d inputs.', $transition->getName(), $name, \count($froms))); } // Enforcing uniqueness of the names of transitions starting at each node $from = reset($froms); if (isset($transitionFromNames[$from][$transition->getName()])) { - throw new InvalidDefinitionException(sprintf('A transition from a place/state must have an unique name. Multiple transitions named "%s" from place/state "%s" were found on StateMachine "%s".', $transition->getName(), $from, $name)); + throw new InvalidDefinitionException(\sprintf('A transition from a place/state must have an unique name. Multiple transitions named "%s" from place/state "%s" were found on StateMachine "%s".', $transition->getName(), $from, $name)); } $transitionFromNames[$from][$transition->getName()] = true; @@ -45,7 +45,7 @@ public function validate(Definition $definition, string $name): void $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The state machine "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(\sprintf('The state machine "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index f9bba8c..f4eb292 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -32,7 +32,7 @@ public function validate(Definition $definition, string $name): void foreach ($definition->getTransitions() as $transition) { foreach ($transition->getFroms() as $from) { if (\in_array($transition->getName(), $places[$from], true)) { - throw new InvalidDefinitionException(sprintf('All transitions for a place must have an unique name. Multiple transitions named "%s" where found for place "%s" in workflow "%s".', $transition->getName(), $from, $name)); + throw new InvalidDefinitionException(\sprintf('All transitions for a place must have an unique name. Multiple transitions named "%s" where found for place "%s" in workflow "%s".', $transition->getName(), $from, $name)); } $places[$from][] = $transition->getName(); } @@ -44,13 +44,13 @@ public function validate(Definition $definition, string $name): void foreach ($definition->getTransitions() as $transition) { if (1 < \count($transition->getTos())) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); + throw new InvalidDefinitionException(\sprintf('The marking store of workflow "%s" cannot store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); } } $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(\sprintf('The marking store of workflow "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } diff --git a/Workflow.php b/Workflow.php index 2a61d48..04b0084 100644 --- a/Workflow.php +++ b/Workflow.php @@ -77,7 +77,7 @@ public function getMarking(object $subject, array $context = []): Marking // check if the subject is already in the workflow if (!$marking->getPlaces()) { if (!$this->definition->getInitialPlaces()) { - throw new LogicException(sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name)); + throw new LogicException(\sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name)); } foreach ($this->definition->getInitialPlaces() as $place) { $marking->mark($place); @@ -97,7 +97,7 @@ public function getMarking(object $subject, array $context = []): Marking $places = $this->definition->getPlaces(); foreach ($marking->getPlaces() as $placeName => $nbToken) { if (!isset($places[$placeName])) { - $message = sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name); + $message = \sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name); if (!$places) { $message .= ' It seems you forgot to add places to the current workflow.'; } @@ -313,8 +313,8 @@ private function guardTransition(object $subject, Marking $marking, Transition $ $event = new GuardEvent($subject, $marking, $transition, $this); $this->dispatcher->dispatch($event, WorkflowEvents::GUARD); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.guard', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.guard.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.guard', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.guard.%s', $this->name, $transition->getName())); return $event; } @@ -327,10 +327,10 @@ private function leave(object $subject, Transition $transition, Marking $marking $event = new LeaveEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::LEAVE); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.leave', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.leave.%s', $this->name, $place)); } } @@ -348,8 +348,8 @@ private function transition(object $subject, Transition $transition, Marking $ma $event = new TransitionEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::TRANSITION); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.transition', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.transition.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.transition', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.transition.%s', $this->name, $transition->getName())); return $event->getContext(); } @@ -362,10 +362,10 @@ private function enter(object $subject, Transition $transition, Marking $marking $event = new EnterEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ENTER); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.enter', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.enter.%s', $this->name, $place)); } } @@ -383,10 +383,10 @@ private function entered(object $subject, ?Transition $transition, Marking $mark $event = new EnteredEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ENTERED); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.entered', $this->name)); foreach ($marking->getPlaces() as $placeName => $nbToken) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.entered.%s', $this->name, $placeName)); } } @@ -399,8 +399,8 @@ private function completed(object $subject, Transition $transition, Marking $mar $event = new CompletedEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::COMPLETED); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.completed', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.completed.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.completed', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.completed.%s', $this->name, $transition->getName())); } private function announce(object $subject, Transition $initialTransition, Marking $marking, array $context): void @@ -412,10 +412,10 @@ private function announce(object $subject, Transition $initialTransition, Markin $event = new AnnounceEvent($subject, $marking, $initialTransition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ANNOUNCE); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.announce', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.announce', $this->name)); foreach ($this->getEnabledTransitions($subject) as $transition) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.announce.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.announce.%s', $this->name, $transition->getName())); } } From 6a04b0a9db64b31d8ac5912e0edd0dd137241c35 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Tue, 25 Jun 2024 12:13:44 +0200 Subject: [PATCH 128/145] Add phpdoc about exception --- WorkflowInterface.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/WorkflowInterface.php b/WorkflowInterface.php index 8e0faef..6f5bff2 100644 --- a/WorkflowInterface.php +++ b/WorkflowInterface.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Workflow; use Symfony\Component\Workflow\Exception\LogicException; +use Symfony\Component\Workflow\Exception\UndefinedTransitionException; use Symfony\Component\Workflow\MarkingStore\MarkingStoreInterface; use Symfony\Component\Workflow\Metadata\MetadataStoreInterface; @@ -38,6 +39,8 @@ public function can(object $subject, string $transitionName): bool; /** * Builds a TransitionBlockerList to know why a transition is blocked. + * + * @throws UndefinedTransitionException If the transition is not defined */ public function buildTransitionBlockerList(object $subject, string $transitionName): TransitionBlockerList; From f83796e5edd9c5611eb1cb115648b075184d71ea Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 6 Jul 2024 09:57:16 +0200 Subject: [PATCH 129/145] Update .gitattributes --- .gitattributes | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 84c7add..14c3c35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore From 432717f77ffea26fe1d2255eb442f3f04c0d1c0d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Wed, 31 Jul 2024 16:13:26 +0200 Subject: [PATCH 130/145] Remove unused code and unnecessary `else` branches --- DataCollector/WorkflowDataCollector.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index ee923a1..febc975 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -90,7 +90,7 @@ public function getCallsCount(): int protected function getCasters(): array { - $casters = [ + return [ ...parent::getCasters(), TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) { unset( @@ -108,8 +108,6 @@ protected function getCasters(): array return $a; }, ]; - - return $casters; } public function hash(string $string): string From a370dba51fe7907f8d169efbe1ac08595a42daec Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Mon, 29 Jul 2024 09:33:48 +0200 Subject: [PATCH 131/145] Remove useless code --- Dumper/PlantUmlDumper.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 6943dca..9bd621a 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -115,7 +115,7 @@ public function dump(Definition $definition, ?Marking $marking = null, array $op } } - return $this->startPuml($options).$this->getLines($code).$this->endPuml($options); + return $this->startPuml().$this->getLines($code).$this->endPuml(); } private function isWorkflowTransitionType(): bool @@ -123,15 +123,12 @@ private function isWorkflowTransitionType(): bool return self::WORKFLOW_TRANSITION === $this->transitionType; } - private function startPuml(array $options): string + private function startPuml(): string { - $start = '@startuml'.\PHP_EOL; - $start .= 'allow_mixing'.\PHP_EOL; - - return $start; + return '@startuml'.\PHP_EOL.'allow_mixing'.\PHP_EOL; } - private function endPuml(array $options): string + private function endPuml(): string { return \PHP_EOL.'@enduml'; } From 2147e6858971405ea0c5a607293c5f08df1b0d6e Mon Sep 17 00:00:00 2001 From: Iain Cambridge Date: Thu, 11 Jul 2024 11:11:35 +0200 Subject: [PATCH 132/145] [Workflow] Clearer `NotEnabledTransitionException` message --- Exception/NotEnabledTransitionException.php | 4 ++-- Tests/WorkflowTest.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index b80693b..26d1c8d 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -15,7 +15,7 @@ use Symfony\Component\Workflow\WorkflowInterface; /** - * Thrown by Workflow when a not enabled transition is applied on a subject. + * Thrown when a transition cannot be applied on a subject. * * @author Grégoire Pineau */ @@ -28,7 +28,7 @@ public function __construct( private TransitionBlockerList $transitionBlockerList, array $context = [], ) { - parent::__construct($subject, $transitionName, $workflow, \sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); + parent::__construct($subject, $transitionName, $workflow, \sprintf('Cannot apply transition "%s" on workflow "%s".', $transitionName, $workflow->getName()), $context); } public function getTransitionBlockerList(): TransitionBlockerList diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 83af790..e78530a 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -286,7 +286,7 @@ public function testApplyWithNotEnabledTransition() $this->fail('Should throw an exception'); } catch (NotEnabledTransitionException $e) { - $this->assertSame('Transition "t2" is not enabled for workflow "unnamed".', $e->getMessage()); + $this->assertSame('Cannot apply transition "t2" on workflow "unnamed".', $e->getMessage()); $this->assertCount(1, $e->getTransitionBlockerList()); $list = iterator_to_array($e->getTransitionBlockerList()); $this->assertSame('The marking does not enable the transition.', $list[0]->getMessage()); From 7649fb810141e31ce622bec14c7c0a4cbc1b33e9 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 24 Sep 2024 12:58:43 +0200 Subject: [PATCH 133/145] Fix `$this` calls to static ones when relevant --- MarkingStore/MethodMarkingStore.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index 50d6169..a2844b7 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -88,7 +88,7 @@ private function getGetter(object $subject): callable $property = $this->property; $method = 'get'.ucfirst($property); - return match ($this->getters[$subject::class] ??= $this->getType($subject, $property, $method)) { + return match ($this->getters[$subject::class] ??= self::getType($subject, $property, $method)) { MarkingStoreMethod::METHOD => $subject->{$method}(...), MarkingStoreMethod::PROPERTY => static fn () => $subject->{$property}, }; @@ -99,7 +99,7 @@ private function getSetter(object $subject): callable $property = $this->property; $method = 'set'.ucfirst($property); - return match ($this->setters[$subject::class] ??= $this->getType($subject, $property, $method)) { + return match ($this->setters[$subject::class] ??= self::getType($subject, $property, $method)) { MarkingStoreMethod::METHOD => $subject->{$method}(...), MarkingStoreMethod::PROPERTY => static fn ($marking) => $subject->{$property} = $marking, }; From 60036931b04cfc9c2ba737e471366fe79cc68468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Mon, 23 Sep 2024 11:24:18 +0200 Subject: [PATCH 134/145] Add PR template and auto-close PR on subtree split repositories --- .gitattributes | 3 +-- .github/PULL_REQUEST_TEMPLATE.md | 8 ++++++++ .github/workflows/close-pull-request.yml | 20 ++++++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/close-pull-request.yml diff --git a/.gitattributes b/.gitattributes index 84c7add..14c3c35 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ /Tests export-ignore /phpunit.xml.dist export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore +/.git* export-ignore diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..4689c4d --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Please do not submit any Pull Requests here. They will be closed. +--- + +Please submit your PR here instead: +https://github.com/symfony/symfony + +This repository is what we call a "subtree split": a read-only subset of that main repository. +We're looking forward to your PR there! diff --git a/.github/workflows/close-pull-request.yml b/.github/workflows/close-pull-request.yml new file mode 100644 index 0000000..e55b478 --- /dev/null +++ b/.github/workflows/close-pull-request.yml @@ -0,0 +1,20 @@ +name: Close Pull Request + +on: + pull_request_target: + types: [opened] + +jobs: + run: + runs-on: ubuntu-latest + steps: + - uses: superbrothers/close-pull-request@v3 + with: + comment: | + Thanks for your Pull Request! We love contributions. + + However, you should instead open your PR on the main repository: + https://github.com/symfony/symfony + + This repository is what we call a "subtree split": a read-only subset of that main repository. + We're looking forward to your PR there! From 74fffece2459b9cf4c542f76010a152a34421bbf Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 10 Dec 2024 09:51:17 +0100 Subject: [PATCH 135/145] [Workflow] Update union to intersection for mock type --- Tests/Debug/TraceableWorkflowTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/Debug/TraceableWorkflowTest.php b/Tests/Debug/TraceableWorkflowTest.php index 3d8e699..257ad66 100644 --- a/Tests/Debug/TraceableWorkflowTest.php +++ b/Tests/Debug/TraceableWorkflowTest.php @@ -21,7 +21,7 @@ class TraceableWorkflowTest extends TestCase { - private MockObject|Workflow $innerWorkflow; + private MockObject&Workflow $innerWorkflow; private Stopwatch $stopwatch; From e63b1886ab3fb3a0bf7acb2a8d614eb48d706b5e Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Sun, 2 Mar 2025 16:03:52 +0100 Subject: [PATCH 136/145] replace assertEmpty() with stricter assertions --- Tests/WorkflowTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index e78530a..18cbaf0 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -734,7 +734,7 @@ public function testGetEnabledTransitions() }); $workflow = new Workflow($definition, new MethodMarkingStore(), $eventDispatcher, 'workflow_name'); - $this->assertEmpty($workflow->getEnabledTransitions($subject)); + $this->assertSame([], $workflow->getEnabledTransitions($subject)); $subject->setMarking(['d' => 1]); $transitions = $workflow->getEnabledTransitions($subject); From 82d0fa52166a953b09ee5f20275078cd6a44acc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 10 Apr 2025 14:54:55 +0200 Subject: [PATCH 137/145] [Workflow] Fix dispatch of entered event when the subject is already in this marking --- Tests/WorkflowTest.php | 39 +++++++++++++++++++++++++++++++++++++++ Workflow.php | 8 +++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/Tests/WorkflowTest.php b/Tests/WorkflowTest.php index 8e112df..543398a 100644 --- a/Tests/WorkflowTest.php +++ b/Tests/WorkflowTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\Event\EnteredEvent; use Symfony\Component\Workflow\Event\Event; use Symfony\Component\Workflow\Event\GuardEvent; use Symfony\Component\Workflow\Event\TransitionEvent; @@ -685,6 +686,44 @@ public function testEventDefaultInitialContext() $workflow->apply($subject, 't1'); } + public function testEventWhenAlreadyInThisPlace() + { + // ┌──────┐ ┌──────────────────────┐ ┌───┐ ┌─────────────┐ ┌───┐ + // │ init │ ──▶ │ from_init_to_a_and_b │ ──▶ │ B │ ──▶ │ from_b_to_c │ ──▶ │ C │ + // └──────┘ └──────────────────────┘ └───┘ └─────────────┘ └───┘ + // │ + // │ + // ▼ + // ┌───────────────────────────────┐ + // │ A │ + // └───────────────────────────────┘ + $definition = new Definition( + ['init', 'A', 'B', 'C'], + [ + new Transition('from_init_to_a_and_b', 'init', ['A', 'B']), + new Transition('from_b_to_c', 'B', 'C'), + ], + ); + + $subject = new Subject(); + $dispatcher = new EventDispatcher(); + $name = 'workflow_name'; + $workflow = new Workflow($definition, new MethodMarkingStore(), $dispatcher, $name); + + $calls = []; + $listener = function (Event $event) use (&$calls) { + $calls[] = $event; + }; + $dispatcher->addListener("workflow.$name.entered.A", $listener); + + $workflow->apply($subject, 'from_init_to_a_and_b'); + $workflow->apply($subject, 'from_b_to_c'); + + $this->assertCount(1, $calls); + $this->assertInstanceOf(EnteredEvent::class, $calls[0]); + $this->assertSame('from_init_to_a_and_b', $calls[0]->getTransition()->getName()); + } + public function testMarkingStateOnApplyWithEventDispatcher() { $definition = new Definition(range('a', 'f'), [new Transition('t', range('a', 'c'), range('d', 'f'))]); diff --git a/Workflow.php b/Workflow.php index 1bad55e..818fbc2 100644 --- a/Workflow.php +++ b/Workflow.php @@ -391,7 +391,13 @@ private function entered(object $subject, ?Transition $transition, Marking $mark $this->dispatcher->dispatch($event, WorkflowEvents::ENTERED); $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered', $this->name)); - foreach ($marking->getPlaces() as $placeName => $nbToken) { + $placeNames = []; + if ($transition) { + $placeNames = $transition->getTos(); + } elseif ($this->definition->getInitialPlaces()) { + $placeNames = $this->definition->getInitialPlaces(); + } + foreach ($placeNames as $placeName) { $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName)); } } From 3990b36584b15b9e532375e49fd2d49abf6f17fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 10 Apr 2025 14:00:01 +0200 Subject: [PATCH 138/145] [Workflow] Add a link to mermaid.live from the profiler --- DataCollector/WorkflowDataCollector.php | 35 +++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index febc975..0cb7e20 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -88,21 +88,39 @@ public function getCallsCount(): int return $i; } + public function hash(string $string): string + { + return hash('xxh128', $string); + } + + public function buildMermaidLiveLink(string $name): string + { + $payload = [ + 'code' => $this->data['workflows'][$name]['dump'], + 'mermaid' => '{"theme": "default"}', + 'autoSync' => false, + ]; + + $compressed = zlib_encode(json_encode($payload), ZLIB_ENCODING_DEFLATE); + + $suffix = rtrim(strtr(base64_encode($compressed), '+/', '-_'), '='); + + return "https://mermaid.live/edit#pako:{$suffix}"; + } + protected function getCasters(): array { return [ ...parent::getCasters(), - TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) { - unset( - $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], - $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], - ); + TransitionBlocker::class => static function ($v, array $a, Stub $s) { + unset($a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')]); + unset($a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')]); $s->cut += 2; return $a; }, - Marking::class => function ($v, array $a, Stub $s, $isNested) { + Marking::class => static function ($v, array $a) { $a[Caster::PREFIX_VIRTUAL.'.places'] = array_keys($v->getPlaces()); return $a; @@ -110,11 +128,6 @@ protected function getCasters(): array ]; } - public function hash(string $string): string - { - return hash('xxh128', $string); - } - private function getEventListeners(WorkflowInterface $workflow): array { $listeners = []; From 492ec937d2f30f9fdd08e202e13196406a0f3eeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Fri, 11 Apr 2025 15:52:48 +0200 Subject: [PATCH 139/145] [Workflow] Add more tests --- Tests/Validator/WorkflowValidatorTest.php | 34 +++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index 036ece7..49f0400 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Workflow\Tests\Validator; use PHPUnit\Framework\TestCase; +use Symfony\Component\Workflow\Arc; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Exception\InvalidDefinitionException; use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; @@ -24,8 +25,6 @@ class WorkflowValidatorTest extends TestCase public function testWorkflowWithInvalidNames() { - $this->expectException(InvalidDefinitionException::class); - $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); $places = range('a', 'c'); $transitions = []; @@ -35,6 +34,9 @@ public function testWorkflowWithInvalidNames() $definition = new Definition($places, $transitions); + $this->expectException(InvalidDefinitionException::class); + $this->expectExceptionMessage('All transitions for a place must have an unique name. Multiple transitions named "t1" where found for place "a" in workflow "foo".'); + (new WorkflowValidator())->validate($definition, 'foo'); } @@ -54,4 +56,32 @@ public function testSameTransitionNameButNotSamePlace() // the test ensures that the validation does not fail (i.e. it does not throw any exceptions) $this->addToAssertionCount(1); } + + public function testWithTooManyOutput() + { + $places = ['a', 'b', 'c']; + $transitions = [ + new Transition('t1', 'a', ['b', 'c']), + ]; + $definition = new Definition($places, $transitions); + + $this->expectException(InvalidDefinitionException::class); + $this->expectExceptionMessage('The marking store of workflow "foo" cannot store many places. But the transition "t1" has too many output (2). Only one is accepted.'); + + (new WorkflowValidator(true))->validate($definition, 'foo'); + } + + public function testWithTooManyInitialPlaces() + { + $places = ['a', 'b', 'c']; + $transitions = [ + new Transition('t1', 'a', 'b'), + ]; + $definition = new Definition($places, $transitions, ['a', 'b']); + + $this->expectException(InvalidDefinitionException::class); + $this->expectExceptionMessage('The marking store of workflow "foo" cannot store many places. But the definition has 2 initial places. Only one is supported.'); + + (new WorkflowValidator(true))->validate($definition, 'foo'); + } } From c1ffe3d69e1e7e1f9eee4300e1c55911fbbec5fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Thu, 10 Apr 2025 16:21:22 +0200 Subject: [PATCH 140/145] [Workflow] Deprecate `Event::getWorkflow()` method --- CHANGELOG.md | 7 ++++++- Event/Event.php | 5 +++++ composer.json | 7 ++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2926da4..5a37ead 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,18 @@ CHANGELOG ========= +7.3 +--- + + * Deprecate `Event::getWorkflow()` method + 7.1 --- * Add method `getEnabledTransition()` to `WorkflowInterface` * Automatically register places from transitions * Add support for workflows that need to store many tokens in the marking - * Add method `getName()` in event classes to build event names in subscribers + * Add method `getName()` in event classes to build event names in subscribers 7.0 --- diff --git a/Event/Event.php b/Event/Event.php index c3e6a6f..c13818b 100644 --- a/Event/Event.php +++ b/Event/Event.php @@ -46,8 +46,13 @@ public function getTransition(): ?Transition return $this->transition; } + /** + * @deprecated since Symfony 7.3, inject the workflow in the constructor where you need it + */ public function getWorkflow(): WorkflowInterface { + trigger_deprecation('symfony/workflow', '7.3', 'The "%s()" method is deprecated, inject the workflow in the constructor where you need it.', __METHOD__); + return $this->workflow; } diff --git a/composer.json b/composer.json index 44a3000..ef6779c 100644 --- a/composer.json +++ b/composer.json @@ -20,15 +20,16 @@ } ], "require": { - "php": ">=8.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "2.5|^3" }, "require-dev": { "psr/log": "^1|^2|^3", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", "symfony/error-handler": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", "symfony/security-core": "^6.4|^7.0", "symfony/stopwatch": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0" From cd0203f9b3ad04a4254385475ff2784811a49998 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 18 Apr 2025 14:51:48 +0200 Subject: [PATCH 141/145] Don't enable tracing unless the profiler is enabled --- Debug/TraceableWorkflow.php | 4 ++++ DependencyInjection/WorkflowDebugPass.php | 1 + 2 files changed, 5 insertions(+) diff --git a/Debug/TraceableWorkflow.php b/Debug/TraceableWorkflow.php index 6d0afd8..c783e63 100644 --- a/Debug/TraceableWorkflow.php +++ b/Debug/TraceableWorkflow.php @@ -30,6 +30,7 @@ class TraceableWorkflow implements WorkflowInterface public function __construct( private readonly WorkflowInterface $workflow, private readonly Stopwatch $stopwatch, + protected readonly ?\Closure $disabled = null, ) { } @@ -90,6 +91,9 @@ public function getCalls(): array private function callInner(string $method, array $args): mixed { + if ($this->disabled?->__invoke()) { + return $this->workflow->{$method}(...$args); + } $sMethod = $this->workflow::class.'::'.$method; $this->stopwatch->start($sMethod, 'workflow'); diff --git a/DependencyInjection/WorkflowDebugPass.php b/DependencyInjection/WorkflowDebugPass.php index 634605d..042aaba 100644 --- a/DependencyInjection/WorkflowDebugPass.php +++ b/DependencyInjection/WorkflowDebugPass.php @@ -31,6 +31,7 @@ public function process(ContainerBuilder $container): void ->setArguments([ new Reference("debug.{$id}.inner"), new Reference('debug.stopwatch'), + new Reference('profiler.is_disabled_state_checker', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), ]); } } From f8490656e99ae9558054a9586a77cfce271b7f7e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 27 Apr 2025 15:26:02 +0200 Subject: [PATCH 142/145] Remove unneeded use statements --- Tests/Validator/WorkflowValidatorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/Validator/WorkflowValidatorTest.php b/Tests/Validator/WorkflowValidatorTest.php index 49f0400..50c3abd 100644 --- a/Tests/Validator/WorkflowValidatorTest.php +++ b/Tests/Validator/WorkflowValidatorTest.php @@ -12,7 +12,6 @@ namespace Symfony\Component\Workflow\Tests\Validator; use PHPUnit\Framework\TestCase; -use Symfony\Component\Workflow\Arc; use Symfony\Component\Workflow\Definition; use Symfony\Component\Workflow\Exception\InvalidDefinitionException; use Symfony\Component\Workflow\Tests\WorkflowBuilderTrait; From 238b84b3e5de703a5c62fd26cb1f2bbe0e3057b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 13 Mar 2024 18:34:52 +0100 Subject: [PATCH 143/145] [Workflow] Add support for executing custom workflow definition validators during the container compilation --- DependencyInjection/WorkflowValidatorPass.php | 37 ++++++++++ .../WorkflowValidatorPassTest.php | 74 +++++++++++++++++++ composer.json | 1 + 3 files changed, 112 insertions(+) create mode 100644 DependencyInjection/WorkflowValidatorPass.php create mode 100644 Tests/DependencyInjection/WorkflowValidatorPassTest.php diff --git a/DependencyInjection/WorkflowValidatorPass.php b/DependencyInjection/WorkflowValidatorPass.php new file mode 100644 index 0000000..60072ef --- /dev/null +++ b/DependencyInjection/WorkflowValidatorPass.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\DependencyInjection; + +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Exception\LogicException; + +/** + * @author Grégoire Pineau + */ +class WorkflowValidatorPass implements CompilerPassInterface +{ + public function process(ContainerBuilder $container): void + { + foreach ($container->findTaggedServiceIds('workflow') as $attributes) { + foreach ($attributes as $attribute) { + foreach ($attribute['definition_validators'] ?? [] as $validatorClass) { + $container->addResource(new FileResource($container->getReflectionClass($validatorClass)->getFileName())); + + $realDefinition = $container->get($attribute['definition_id'] ?? throw new \LogicException('The "definition_id" attribute is required.')); + (new $validatorClass())->validate($realDefinition, $attribute['name'] ?? throw new \LogicException('The "name" attribute is required.')); + } + } + } + } +} diff --git a/Tests/DependencyInjection/WorkflowValidatorPassTest.php b/Tests/DependencyInjection/WorkflowValidatorPassTest.php new file mode 100644 index 0000000..213e0d4 --- /dev/null +++ b/Tests/DependencyInjection/WorkflowValidatorPassTest.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow\Tests\DependencyInjection; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\Workflow\Definition; +use Symfony\Component\Workflow\DependencyInjection\WorkflowValidatorPass; +use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface; +use Symfony\Component\Workflow\WorkflowInterface; + +class WorkflowValidatorPassTest extends TestCase +{ + private ContainerBuilder $container; + private WorkflowValidatorPass $compilerPass; + + protected function setUp(): void + { + $this->container = new ContainerBuilder(); + $this->compilerPass = new WorkflowValidatorPass(); + } + + public function testNothingToDo() + { + $this->compilerPass->process($this->container); + + $this->assertFalse(DefinitionValidator::$called); + } + + public function testValidate() + { + $this + ->container + ->register('my.workflow', WorkflowInterface::class) + ->addTag('workflow', [ + 'definition_id' => 'my.workflow.definition', + 'name' => 'my.workflow', + 'definition_validators' => [DefinitionValidator::class], + ]) + ; + + $this + ->container + ->register('my.workflow.definition', Definition::class) + ->setArguments([ + '$places' => [], + '$transitions' => [], + ]) + ; + + $this->compilerPass->process($this->container); + + $this->assertTrue(DefinitionValidator::$called); + } +} + +class DefinitionValidator implements DefinitionValidatorInterface +{ + public static bool $called = false; + + public function validate(Definition $definition, string $name): void + { + self::$called = true; + } +} diff --git a/composer.json b/composer.json index ef6779c..3e2c50a 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ }, "require-dev": { "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/error-handler": "^6.4|^7.0", "symfony/event-dispatcher": "^6.4|^7.0", From ffcbdf0c33e4534912d1174bbfd0549dd3bb10f3 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 10 Jul 2025 09:12:18 +0200 Subject: [PATCH 144/145] CS fixes --- Attribute/BuildEventNameTrait.php | 8 ++--- DataCollector/WorkflowDataCollector.php | 16 +++++----- Definition.php | 6 ++-- .../WorkflowGuardListenerPass.php | 2 +- Dumper/GraphvizDumper.php | 32 +++++++++---------- Dumper/MermaidDumper.php | 22 ++++++------- Dumper/PlantUmlDumper.php | 6 ++-- Dumper/StateMachineGraphvizDumper.php | 2 +- EventListener/AuditTrailListener.php | 6 ++-- EventListener/ExpressionLanguage.php | 4 +-- Exception/NotEnabledTransitionException.php | 2 +- Exception/UndefinedTransitionException.php | 2 +- MarkingStore/MethodMarkingStore.php | 6 ++-- Registry.php | 4 +-- Tests/Attribute/AsListenerTest.php | 4 +-- Tests/Debug/TraceableWorkflowTest.php | 2 +- Tests/Dumper/MermaidDumperTest.php | 12 +++---- Tests/StateMachineTest.php | 4 +-- Validator/StateMachineValidator.php | 8 ++--- Validator/WorkflowValidator.php | 6 ++-- Workflow.php | 32 +++++++++---------- 21 files changed, 93 insertions(+), 93 deletions(-) diff --git a/Attribute/BuildEventNameTrait.php b/Attribute/BuildEventNameTrait.php index 93eeee7..d6d3c26 100644 --- a/Attribute/BuildEventNameTrait.php +++ b/Attribute/BuildEventNameTrait.php @@ -24,16 +24,16 @@ private static function buildEventName(string $keyword, string $argument, ?strin { if (null === $workflow) { if (null !== $node) { - throw new LogicException(sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class)); + throw new LogicException(\sprintf('The "%s" argument of "%s" cannot be used without a "workflow" argument.', $argument, self::class)); } - return sprintf('workflow.%s', $keyword); + return \sprintf('workflow.%s', $keyword); } if (null === $node) { - return sprintf('workflow.%s.%s', $workflow, $keyword); + return \sprintf('workflow.%s.%s', $workflow, $keyword); } - return sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node); + return \sprintf('workflow.%s.%s.%s', $workflow, $keyword, $node); } } diff --git a/DataCollector/WorkflowDataCollector.php b/DataCollector/WorkflowDataCollector.php index cf15802..cee5504 100644 --- a/DataCollector/WorkflowDataCollector.php +++ b/DataCollector/WorkflowDataCollector.php @@ -94,8 +94,8 @@ protected function getCasters(): array ...parent::getCasters(), TransitionBlocker::class => function ($v, array $a, Stub $s, $isNested) { unset( - $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], - $a[sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], + $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'code')], + $a[\sprintf(Caster::PATTERN_PRIVATE, $v::class, 'parameters')], ); $s->cut += 2; @@ -129,9 +129,9 @@ private function getEventListeners(WorkflowInterface $workflow): array 'entered', ]; foreach ($subEventNames as $subEventName) { - $eventNames[] = sprintf('workflow.%s', $subEventName); - $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); - $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $place); + $eventNames[] = \sprintf('workflow.%s', $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $place); } foreach ($eventNames as $eventName) { foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { @@ -151,9 +151,9 @@ private function getEventListeners(WorkflowInterface $workflow): array 'announce', ]; foreach ($subEventNames as $subEventName) { - $eventNames[] = sprintf('workflow.%s', $subEventName); - $eventNames[] = sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); - $eventNames[] = sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $transition->getName()); + $eventNames[] = \sprintf('workflow.%s', $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s', $workflow->getName(), $subEventName); + $eventNames[] = \sprintf('workflow.%s.%s.%s', $workflow->getName(), $subEventName, $transition->getName()); } foreach ($eventNames as $eventName) { foreach ($this->eventDispatcher->getListeners($eventName) as $listener) { diff --git a/Definition.php b/Definition.php index e876b9f..bf8e888 100644 --- a/Definition.php +++ b/Definition.php @@ -89,7 +89,7 @@ private function setInitialPlaces(string|array|null $places = null): void foreach ($places as $place) { if (!isset($this->places[$place])) { - throw new LogicException(sprintf('Place "%s" cannot be the initial place as it does not exist.', $place)); + throw new LogicException(\sprintf('Place "%s" cannot be the initial place as it does not exist.', $place)); } } @@ -111,13 +111,13 @@ private function addTransition(Transition $transition): void foreach ($transition->getFroms() as $from) { if (!isset($this->places[$from])) { - throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $from, $name)); + throw new LogicException(\sprintf('Place "%s" referenced in transition "%s" does not exist.', $from, $name)); } } foreach ($transition->getTos() as $to) { if (!isset($this->places[$to])) { - throw new LogicException(sprintf('Place "%s" referenced in transition "%s" does not exist.', $to, $name)); + throw new LogicException(\sprintf('Place "%s" referenced in transition "%s" does not exist.', $to, $name)); } } diff --git a/DependencyInjection/WorkflowGuardListenerPass.php b/DependencyInjection/WorkflowGuardListenerPass.php index ba81a7b..ccf00f0 100644 --- a/DependencyInjection/WorkflowGuardListenerPass.php +++ b/DependencyInjection/WorkflowGuardListenerPass.php @@ -38,7 +38,7 @@ public function process(ContainerBuilder $container): void foreach ($servicesNeeded as $service) { if (!$container->has($service)) { - throw new LogicException(sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); + throw new LogicException(\sprintf('The "%s" service is needed to be able to use the workflow guard listener.', $service)); } } } diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 9a99690..36a5be9 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -154,14 +154,14 @@ protected function addPlaces(array $places, float $withMetadata): string } if ($withMetadata) { - $escapedLabel = sprintf('<%s%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); + $escapedLabel = \sprintf('<%s%s>', $this->escape($placeName), $this->addMetadata($place['attributes']['metadata'])); // Don't include metadata in default attributes used to format the place unset($place['attributes']['metadata']); } else { - $escapedLabel = sprintf('"%s"', $this->escape($placeName)); + $escapedLabel = \sprintf('"%s"', $this->escape($placeName)); } - $code .= sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); + $code .= \sprintf(" place_%s [label=%s, shape=circle%s];\n", $this->dotize($id), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -176,12 +176,12 @@ protected function addTransitions(array $transitions, bool $withMetadata): strin foreach ($transitions as $i => $place) { if ($withMetadata) { - $escapedLabel = sprintf('<%s%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); + $escapedLabel = \sprintf('<%s%s>', $this->escape($place['name']), $this->addMetadata($place['metadata'])); } else { $escapedLabel = '"'.$this->escape($place['name']).'"'; } - $code .= sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); + $code .= \sprintf(" transition_%s [label=%s,%s];\n", $this->dotize($i), $escapedLabel, $this->addAttributes($place['attributes'])); } return $code; @@ -229,12 +229,12 @@ protected function addEdges(array $edges): string foreach ($edges as $edge) { if ('from' === $edge['direction']) { - $code .= sprintf(" place_%s -> transition_%s [style=\"solid\"];\n", + $code .= \sprintf(" place_%s -> transition_%s [style=\"solid\"];\n", $this->dotize($edge['from']), $this->dotize($edge['transition_number']) ); } else { - $code .= sprintf(" transition_%s -> place_%s [style=\"solid\"];\n", + $code .= \sprintf(" transition_%s -> place_%s [style=\"solid\"];\n", $this->dotize($edge['transition_number']), $this->dotize($edge['to']) ); @@ -249,9 +249,9 @@ protected function addEdges(array $edges): string */ protected function startDot(array $options, string $label): string { - return sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", + return \sprintf("digraph workflow {\n %s%s\n node [%s];\n edge [%s];\n\n", $this->addOptions($options['graph']), - '""' !== $label && '<>' !== $label ? sprintf(' label=%s', $label) : '', + '""' !== $label && '<>' !== $label ? \sprintf(' label=%s', $label) : '', $this->addOptions($options['node']), $this->addOptions($options['edge']) ); @@ -289,7 +289,7 @@ protected function addAttributes(array $attributes): string $code = []; foreach ($attributes as $k => $v) { - $code[] = sprintf('%s="%s"', $k, $this->escape($v)); + $code[] = \sprintf('%s="%s"', $k, $this->escape($v)); } return $code ? ' '.implode(' ', $code) : ''; @@ -309,17 +309,17 @@ protected function formatLabel(Definition $definition, string $withMetadata, arr if (!$withMetadata) { // Only currentLabel to handle. If null, will be translated to empty string - return sprintf('"%s"', $this->escape($currentLabel)); + return \sprintf('"%s"', $this->escape($currentLabel)); } $workflowMetadata = $definition->getMetadataStore()->getWorkflowMetadata(); if ('' === $currentLabel) { // Only metadata to handle - return sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); + return \sprintf('<%s>', $this->addMetadata($workflowMetadata, false)); } // currentLabel and metadata to handle - return sprintf('<%s%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); + return \sprintf('<%s%s>', $this->escape($currentLabel), $this->addMetadata($workflowMetadata)); } private function addOptions(array $options): string @@ -327,7 +327,7 @@ private function addOptions(array $options): string $code = []; foreach ($options as $k => $v) { - $code[] = sprintf('%s="%s"', $k, $v); + $code[] = \sprintf('%s="%s"', $k, $v); } return implode(' ', $code); @@ -344,10 +344,10 @@ private function addMetadata(array $metadata, bool $lineBreakFirstIfNotEmpty = t foreach ($metadata as $key => $value) { if ($skipSeparator) { - $code[] = sprintf('%s: %s', $this->escape($key), $this->escape($value)); + $code[] = \sprintf('%s: %s', $this->escape($key), $this->escape($value)); $skipSeparator = false; } else { - $code[] = sprintf('%s%s: %s', '
', $this->escape($key), $this->escape($value)); + $code[] = \sprintf('%s%s: %s', '
', $this->escape($key), $this->escape($value)); } } diff --git a/Dumper/MermaidDumper.php b/Dumper/MermaidDumper.php index d2f2d6e..69220ee 100644 --- a/Dumper/MermaidDumper.php +++ b/Dumper/MermaidDumper.php @@ -142,7 +142,7 @@ private function preparePlace(int $placeId, string $placeName, array $meta, bool $placeNodeName = 'place'.$placeId; $placeNodeFormat = '%s'.$labelShape; - $placeNode = sprintf($placeNodeFormat, $placeNodeName, $placeLabel); + $placeNode = \sprintf($placeNodeFormat, $placeNodeName, $placeLabel); $placeStyle = $this->styleNode($meta, $placeNodeName, $hasMarking); @@ -154,7 +154,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal $nodeStyles = []; if (\array_key_exists('bg_color', $meta)) { - $nodeStyles[] = sprintf( + $nodeStyles[] = \sprintf( 'fill:%s', $meta['bg_color'] ); @@ -168,7 +168,7 @@ private function styleNode(array $meta, string $nodeName, bool $hasMarking = fal return ''; } - return sprintf('style %s %s', $nodeName, implode(',', $nodeStyles)); + return \sprintf('style %s %s', $nodeName, implode(',', $nodeStyles)); } /** @@ -179,26 +179,26 @@ private function escape(string $label): string { $label = str_replace('"', '#quot;', $label); - return sprintf('"%s"', $label); + return \sprintf('"%s"', $label); } public function validateDirection(string $direction): void { if (!\in_array($direction, self::VALID_DIRECTIONS, true)) { - throw new InvalidArgumentException(sprintf('Direction "%s" is not valid, valid directions are: "%s".', $direction, implode(', ', self::VALID_DIRECTIONS))); + throw new InvalidArgumentException(\sprintf('Direction "%s" is not valid, valid directions are: "%s".', $direction, implode(', ', self::VALID_DIRECTIONS))); } } private function validateTransitionType(string $transitionType): void { if (!\in_array($transitionType, self::VALID_TRANSITION_TYPES, true)) { - throw new InvalidArgumentException(sprintf('Transition type "%s" is not valid, valid types are: "%s".', $transitionType, implode(', ', self::VALID_TRANSITION_TYPES))); + throw new InvalidArgumentException(\sprintf('Transition type "%s" is not valid, valid types are: "%s".', $transitionType, implode(', ', self::VALID_TRANSITION_TYPES))); } } private function styleStateMachineTransition(string $from, string $to, string $transitionLabel, array $transitionMeta): array { - $transitionOutput = [sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; + $transitionOutput = [\sprintf('%s-->|%s|%s', $from, str_replace("\n", ' ', $this->escape($transitionLabel)), $to)]; $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -217,7 +217,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti $transitionLabel = $this->escape($transitionLabel); $transitionNodeName = 'transition'.$transitionId; - $transitionOutput[] = sprintf('%s[%s]', $transitionNodeName, $transitionLabel); + $transitionOutput[] = \sprintf('%s[%s]', $transitionNodeName, $transitionLabel); $transitionNodeStyle = $this->styleNode($transitionMeta, $transitionNodeName); if ('' !== $transitionNodeStyle) { @@ -225,7 +225,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti } $connectionStyle = '%s-->%s'; - $transitionOutput[] = sprintf($connectionStyle, $from, $transitionNodeName); + $transitionOutput[] = \sprintf($connectionStyle, $from, $transitionNodeName); $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -234,7 +234,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti ++$this->linkCount; - $transitionOutput[] = sprintf($connectionStyle, $transitionNodeName, $to); + $transitionOutput[] = \sprintf($connectionStyle, $transitionNodeName, $to); $linkStyle = $this->styleLink($transitionMeta); if ('' !== $linkStyle) { @@ -249,7 +249,7 @@ private function styleWorkflowTransition(string $from, string $to, int $transiti private function styleLink(array $transitionMeta): string { if (\array_key_exists('color', $transitionMeta)) { - return sprintf('linkStyle %d stroke:%s', $this->linkCount, $transitionMeta['color']); + return \sprintf('linkStyle %d stroke:%s', $this->linkCount, $transitionMeta['color']); } return ''; diff --git a/Dumper/PlantUmlDumper.php b/Dumper/PlantUmlDumper.php index 2a232d4..e2f5859 100644 --- a/Dumper/PlantUmlDumper.php +++ b/Dumper/PlantUmlDumper.php @@ -228,9 +228,9 @@ private function getTransitionEscapedWithStyle(MetadataStoreInterface $workflowM if (null !== $color) { // Close and open before and after every '\n' string, // so that the style is applied properly on every line - $to = str_replace('\n', sprintf('\n', $color), $to); + $to = str_replace('\n', \sprintf('\n', $color), $to); - $to = sprintf( + $to = \sprintf( '%2$s', $color, $to @@ -247,7 +247,7 @@ private function getTransitionColor(string $color): string $color = '#'.$color; } - return sprintf('[%s]', $color); + return \sprintf('[%s]', $color); } private function getColorId(string $color): string diff --git a/Dumper/StateMachineGraphvizDumper.php b/Dumper/StateMachineGraphvizDumper.php index e054cb4..7bd9d73 100644 --- a/Dumper/StateMachineGraphvizDumper.php +++ b/Dumper/StateMachineGraphvizDumper.php @@ -89,7 +89,7 @@ protected function addEdges(array $edges): string foreach ($edges as $id => $edges) { foreach ($edges as $edge) { - $code .= sprintf( + $code .= \sprintf( " place_%s -> place_%s [label=\"%s\" style=\"%s\"%s];\n", $this->dotize($id), $this->dotize($edge['to']), diff --git a/EventListener/AuditTrailListener.php b/EventListener/AuditTrailListener.php index 8d82824..6f50382 100644 --- a/EventListener/AuditTrailListener.php +++ b/EventListener/AuditTrailListener.php @@ -33,7 +33,7 @@ public function __construct(LoggerInterface $logger) public function onLeave(Event $event) { foreach ($event->getTransition()->getFroms() as $place) { - $this->logger->info(sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Leaving "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } @@ -42,7 +42,7 @@ public function onLeave(Event $event) */ public function onTransition(Event $event) { - $this->logger->info(sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Transition "%s" for subject of class "%s" in workflow "%s".', $event->getTransition()->getName(), $event->getSubject()::class, $event->getWorkflowName())); } /** @@ -51,7 +51,7 @@ public function onTransition(Event $event) public function onEnter(Event $event) { foreach ($event->getTransition()->getTos() as $place) { - $this->logger->info(sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); + $this->logger->info(\sprintf('Entering "%s" for subject of class "%s" in workflow "%s".', $place, $event->getSubject()::class, $event->getWorkflowName())); } } diff --git a/EventListener/ExpressionLanguage.php b/EventListener/ExpressionLanguage.php index 82fe165..7848fb3 100644 --- a/EventListener/ExpressionLanguage.php +++ b/EventListener/ExpressionLanguage.php @@ -29,9 +29,9 @@ protected function registerFunctions() { parent::registerFunctions(); - $this->register('is_granted', fn ($attributes, $object = 'null') => sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); + $this->register('is_granted', fn ($attributes, $object = 'null') => \sprintf('$auth_checker->isGranted(%s, %s)', $attributes, $object), fn (array $variables, $attributes, $object = null) => $variables['auth_checker']->isGranted($attributes, $object)); - $this->register('is_valid', fn ($object = 'null', $groups = 'null') => sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { + $this->register('is_valid', fn ($object = 'null', $groups = 'null') => \sprintf('0 === count($validator->validate(%s, null, %s))', $object, $groups), function (array $variables, $object = null, $groups = null) { if (!$variables['validator'] instanceof ValidatorInterface) { throw new RuntimeException('"is_valid" cannot be used as the Validator component is not installed. Try running "composer require symfony/validator".'); } diff --git a/Exception/NotEnabledTransitionException.php b/Exception/NotEnabledTransitionException.php index 4144caf..543478c 100644 --- a/Exception/NotEnabledTransitionException.php +++ b/Exception/NotEnabledTransitionException.php @@ -25,7 +25,7 @@ class NotEnabledTransitionException extends TransitionException public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, TransitionBlockerList $transitionBlockerList, array $context = []) { - parent::__construct($subject, $transitionName, $workflow, sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); + parent::__construct($subject, $transitionName, $workflow, \sprintf('Transition "%s" is not enabled for workflow "%s".', $transitionName, $workflow->getName()), $context); $this->transitionBlockerList = $transitionBlockerList; } diff --git a/Exception/UndefinedTransitionException.php b/Exception/UndefinedTransitionException.php index 75d3848..5a8ecf8 100644 --- a/Exception/UndefinedTransitionException.php +++ b/Exception/UndefinedTransitionException.php @@ -22,6 +22,6 @@ class UndefinedTransitionException extends TransitionException { public function __construct(object $subject, string $transitionName, WorkflowInterface $workflow, array $context = []) { - parent::__construct($subject, $transitionName, $workflow, sprintf('Transition "%s" is not defined for workflow "%s".', $transitionName, $workflow->getName()), $context); + parent::__construct($subject, $transitionName, $workflow, \sprintf('Transition "%s" is not defined for workflow "%s".', $transitionName, $workflow->getName()), $context); } } diff --git a/MarkingStore/MethodMarkingStore.php b/MarkingStore/MethodMarkingStore.php index 773328f..50d6169 100644 --- a/MarkingStore/MethodMarkingStore.php +++ b/MarkingStore/MethodMarkingStore.php @@ -53,7 +53,7 @@ public function getMarking(object $subject): Marking try { $marking = ($this->getGetter($subject))(); } catch (\Error $e) { - $unInitializedPropertyMessage = sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); + $unInitializedPropertyMessage = \sprintf('Typed property %s::$%s must not be accessed before initialization', get_debug_type($subject), $this->property); if ($e->getMessage() !== $unInitializedPropertyMessage) { throw $e; } @@ -66,7 +66,7 @@ public function getMarking(object $subject): Marking if ($this->singleState) { $marking = [(string) $marking => 1]; } elseif (!\is_array($marking)) { - throw new LogicException(sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property)); + throw new LogicException(\sprintf('The marking stored in "%s::$%s" is not an array and the Workflow\'s Marking store is instantiated with $singleState=false.', get_debug_type($subject), $this->property)); } return new Marking($marking); @@ -118,7 +118,7 @@ private static function getType(object $subject, string $property, string $metho } catch (\ReflectionException) { } - throw new LogicException(sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property)); + throw new LogicException(\sprintf('Cannot store marking: class "%s" should have either a public method named "%s()" or a public property named "$%s"; none found.', get_debug_type($subject), $method, $property)); } } diff --git a/Registry.php b/Registry.php index 8041e98..96be3cc 100644 --- a/Registry.php +++ b/Registry.php @@ -52,13 +52,13 @@ public function get(object $subject, ?string $workflowName = null): WorkflowInte } if (!$matched) { - throw new InvalidArgumentException(sprintf('Unable to find a workflow for class "%s".', get_debug_type($subject))); + throw new InvalidArgumentException(\sprintf('Unable to find a workflow for class "%s".', get_debug_type($subject))); } if (2 <= \count($matched)) { $names = array_map(static fn (WorkflowInterface $workflow): string => $workflow->getName(), $matched); - throw new InvalidArgumentException(sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); + throw new InvalidArgumentException(\sprintf('Too many workflows (%s) match this subject (%s); set a different name on each and use the second (name) argument of this method.', implode(', ', $names), get_debug_type($subject))); } return $matched[0]; diff --git a/Tests/Attribute/AsListenerTest.php b/Tests/Attribute/AsListenerTest.php index a858626..0a8c232 100644 --- a/Tests/Attribute/AsListenerTest.php +++ b/Tests/Attribute/AsListenerTest.php @@ -64,7 +64,7 @@ public static function provideOkTests(): iterable public function testTransitionThrowException(string $class) { $this->expectException(LogicException::class); - $this->expectExceptionMessage(sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class)); + $this->expectExceptionMessage(\sprintf('The "transition" argument of "%s" cannot be used without a "workflow" argument.', $class)); new $class(transition: 'some'); } @@ -83,7 +83,7 @@ public static function provideTransitionThrowException(): iterable public function testPlaceThrowException(string $class) { $this->expectException(LogicException::class); - $this->expectExceptionMessage(sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class)); + $this->expectExceptionMessage(\sprintf('The "place" argument of "%s" cannot be used without a "workflow" argument.', $class)); new $class(place: 'some'); } diff --git a/Tests/Debug/TraceableWorkflowTest.php b/Tests/Debug/TraceableWorkflowTest.php index 5bfcee9..3d8e699 100644 --- a/Tests/Debug/TraceableWorkflowTest.php +++ b/Tests/Debug/TraceableWorkflowTest.php @@ -23,7 +23,7 @@ class TraceableWorkflowTest extends TestCase { private MockObject|Workflow $innerWorkflow; - private StopWatch $stopwatch; + private Stopwatch $stopwatch; private TraceableWorkflow $traceableWorkflow; diff --git a/Tests/Dumper/MermaidDumperTest.php b/Tests/Dumper/MermaidDumperTest.php index 3a29da6..a8d1978 100644 --- a/Tests/Dumper/MermaidDumperTest.php +++ b/Tests/Dumper/MermaidDumperTest.php @@ -104,7 +104,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."transition4-->place6\n" ."transition5[\"t6\"]\n" ."place5-->transition5\n" - ."transition5-->place6", + .'transition5-->place6', ]; yield [ self::createWorkflowWithSameNameTransition(), @@ -124,7 +124,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."transition2-->place0\n" ."transition3[\"to_a\"]\n" ."place2-->transition3\n" - ."transition3-->place0", + .'transition3-->place0', ]; yield [ self::createSimpleWorkflowDefinition(), @@ -140,7 +140,7 @@ public static function provideWorkflowDefinitionWithoutMarking(): iterable ."linkStyle 1 stroke:Grey\n" ."transition1[\"t2\"]\n" ."place1-->transition1\n" - ."transition1-->place2", + .'transition1-->place2', ]; } @@ -169,7 +169,7 @@ public static function provideWorkflowWithReservedWords(): iterable ."place1-->transition0\n" ."transition1[\"t1\"]\n" ."place2-->transition1\n" - ."transition1-->place3", + .'transition1-->place3', ]; } @@ -186,7 +186,7 @@ public static function provideStateMachine(): iterable ."place3-->|\"My custom transition label 3\"|place1\n" ."linkStyle 1 stroke:Grey\n" ."place1-->|\"t2\"|place2\n" - ."place1-->|\"t3\"|place3", + .'place1-->|"t3"|place3', ]; } @@ -212,7 +212,7 @@ public static function provideWorkflowWithMarking(): iterable ."linkStyle 1 stroke:Grey\n" ."transition1[\"t2\"]\n" ."place1-->transition1\n" - ."transition1-->place2", + .'transition1-->place2', ]; } } diff --git a/Tests/StateMachineTest.php b/Tests/StateMachineTest.php index e991707..5d10fde 100644 --- a/Tests/StateMachineTest.php +++ b/Tests/StateMachineTest.php @@ -88,7 +88,7 @@ public function testBuildTransitionBlockerListReturnsExpectedReasonOnBranchMerge $net = new StateMachine($definition, null, $dispatcher); $dispatcher->addListener('workflow.guard', function (GuardEvent $event) { - $event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); + $event->addTransitionBlocker(new TransitionBlocker(\sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); }); $subject = new Subject(); @@ -124,7 +124,7 @@ public function testApplyReturnsExpectedReasonOnBranchMerge() $net = new StateMachine($definition, null, $dispatcher); $dispatcher->addListener('workflow.guard', function (GuardEvent $event) { - $event->addTransitionBlocker(new TransitionBlocker(sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); + $event->addTransitionBlocker(new TransitionBlocker(\sprintf('Transition blocker of place %s', $event->getTransition()->getFroms()[0]), 'blocker')); }); $subject = new Subject(); diff --git a/Validator/StateMachineValidator.php b/Validator/StateMachineValidator.php index 20afc8d..65fd665 100644 --- a/Validator/StateMachineValidator.php +++ b/Validator/StateMachineValidator.php @@ -28,19 +28,19 @@ public function validate(Definition $definition, string $name) foreach ($definition->getTransitions() as $transition) { // Make sure that each transition has exactly one TO if (1 !== \count($transition->getTos())) { - throw new InvalidDefinitionException(sprintf('A transition in StateMachine can only have one output. But the transition "%s" in StateMachine "%s" has %d outputs.', $transition->getName(), $name, \count($transition->getTos()))); + throw new InvalidDefinitionException(\sprintf('A transition in StateMachine can only have one output. But the transition "%s" in StateMachine "%s" has %d outputs.', $transition->getName(), $name, \count($transition->getTos()))); } // Make sure that each transition has exactly one FROM $froms = $transition->getFroms(); if (1 !== \count($froms)) { - throw new InvalidDefinitionException(sprintf('A transition in StateMachine can only have one input. But the transition "%s" in StateMachine "%s" has %d inputs.', $transition->getName(), $name, \count($froms))); + throw new InvalidDefinitionException(\sprintf('A transition in StateMachine can only have one input. But the transition "%s" in StateMachine "%s" has %d inputs.', $transition->getName(), $name, \count($froms))); } // Enforcing uniqueness of the names of transitions starting at each node $from = reset($froms); if (isset($transitionFromNames[$from][$transition->getName()])) { - throw new InvalidDefinitionException(sprintf('A transition from a place/state must have an unique name. Multiple transitions named "%s" from place/state "%s" were found on StateMachine "%s".', $transition->getName(), $from, $name)); + throw new InvalidDefinitionException(\sprintf('A transition from a place/state must have an unique name. Multiple transitions named "%s" from place/state "%s" were found on StateMachine "%s".', $transition->getName(), $from, $name)); } $transitionFromNames[$from][$transition->getName()] = true; @@ -48,7 +48,7 @@ public function validate(Definition $definition, string $name) $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The state machine "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(\sprintf('The state machine "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } diff --git a/Validator/WorkflowValidator.php b/Validator/WorkflowValidator.php index c13c281..e3bee6d 100644 --- a/Validator/WorkflowValidator.php +++ b/Validator/WorkflowValidator.php @@ -37,7 +37,7 @@ public function validate(Definition $definition, string $name) foreach ($definition->getTransitions() as $transition) { foreach ($transition->getFroms() as $from) { if (\in_array($transition->getName(), $places[$from])) { - throw new InvalidDefinitionException(sprintf('All transitions for a place must have an unique name. Multiple transitions named "%s" where found for place "%s" in workflow "%s".', $transition->getName(), $from, $name)); + throw new InvalidDefinitionException(\sprintf('All transitions for a place must have an unique name. Multiple transitions named "%s" where found for place "%s" in workflow "%s".', $transition->getName(), $from, $name)); } $places[$from][] = $transition->getName(); } @@ -49,13 +49,13 @@ public function validate(Definition $definition, string $name) foreach ($definition->getTransitions() as $transition) { if (1 < \count($transition->getTos())) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); + throw new InvalidDefinitionException(\sprintf('The marking store of workflow "%s" cannot store many places. But the transition "%s" has too many output (%d). Only one is accepted.', $name, $transition->getName(), \count($transition->getTos()))); } } $initialPlaces = $definition->getInitialPlaces(); if (2 <= \count($initialPlaces)) { - throw new InvalidDefinitionException(sprintf('The marking store of workflow "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); + throw new InvalidDefinitionException(\sprintf('The marking store of workflow "%s" cannot store many places. But the definition has %d initial places. Only one is supported.', $name, \count($initialPlaces))); } } } diff --git a/Workflow.php b/Workflow.php index 818fbc2..dee280b 100644 --- a/Workflow.php +++ b/Workflow.php @@ -83,7 +83,7 @@ public function getMarking(object $subject, array $context = []): Marking // check if the subject is already in the workflow if (!$marking->getPlaces()) { if (!$this->definition->getInitialPlaces()) { - throw new LogicException(sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name)); + throw new LogicException(\sprintf('The Marking is empty and there is no initial place for workflow "%s".', $this->name)); } foreach ($this->definition->getInitialPlaces() as $place) { $marking->mark($place); @@ -103,7 +103,7 @@ public function getMarking(object $subject, array $context = []): Marking $places = $this->definition->getPlaces(); foreach ($marking->getPlaces() as $placeName => $nbToken) { if (!isset($places[$placeName])) { - $message = sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name); + $message = \sprintf('Place "%s" is not valid for workflow "%s".', $placeName, $this->name); if (!$places) { $message .= ' It seems you forgot to add places to the current workflow.'; } @@ -319,8 +319,8 @@ private function guardTransition(object $subject, Marking $marking, Transition $ $event = new GuardEvent($subject, $marking, $transition, $this); $this->dispatcher->dispatch($event, WorkflowEvents::GUARD); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.guard', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.guard.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.guard', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.guard.%s', $this->name, $transition->getName())); return $event; } @@ -333,10 +333,10 @@ private function leave(object $subject, Transition $transition, Marking $marking $event = new LeaveEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::LEAVE); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.leave', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.leave.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.leave.%s', $this->name, $place)); } } @@ -354,8 +354,8 @@ private function transition(object $subject, Transition $transition, Marking $ma $event = new TransitionEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::TRANSITION); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.transition', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.transition.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.transition', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.transition.%s', $this->name, $transition->getName())); return $event->getContext(); } @@ -368,10 +368,10 @@ private function enter(object $subject, Transition $transition, Marking $marking $event = new EnterEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ENTER); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.enter', $this->name)); foreach ($places as $place) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.enter.%s', $this->name, $place)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.enter.%s', $this->name, $place)); } } @@ -389,7 +389,7 @@ private function entered(object $subject, ?Transition $transition, Marking $mark $event = new EnteredEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ENTERED); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.entered', $this->name)); $placeNames = []; if ($transition) { @@ -398,7 +398,7 @@ private function entered(object $subject, ?Transition $transition, Marking $mark $placeNames = $this->definition->getInitialPlaces(); } foreach ($placeNames as $placeName) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.entered.%s', $this->name, $placeName)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.entered.%s', $this->name, $placeName)); } } @@ -411,8 +411,8 @@ private function completed(object $subject, Transition $transition, Marking $mar $event = new CompletedEvent($subject, $marking, $transition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::COMPLETED); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.completed', $this->name)); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.completed.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.completed', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.completed.%s', $this->name, $transition->getName())); } private function announce(object $subject, Transition $initialTransition, Marking $marking, array $context): void @@ -424,10 +424,10 @@ private function announce(object $subject, Transition $initialTransition, Markin $event = new AnnounceEvent($subject, $marking, $initialTransition, $this, $context); $this->dispatcher->dispatch($event, WorkflowEvents::ANNOUNCE); - $this->dispatcher->dispatch($event, sprintf('workflow.%s.announce', $this->name)); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.announce', $this->name)); foreach ($this->getEnabledTransitions($subject) as $transition) { - $this->dispatcher->dispatch($event, sprintf('workflow.%s.announce.%s', $this->name, $transition->getName())); + $this->dispatcher->dispatch($event, \sprintf('workflow.%s.announce.%s', $this->name, $transition->getName())); } } From afbcdbebf4e5e597795026601c9bacc03a5df2cb Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Tue, 24 Jun 2025 14:17:47 +0100 Subject: [PATCH 145/145] Fix various bool-type coercions --- Dumper/GraphvizDumper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dumper/GraphvizDumper.php b/Dumper/GraphvizDumper.php index 36a5be9..652e886 100644 --- a/Dumper/GraphvizDumper.php +++ b/Dumper/GraphvizDumper.php @@ -141,7 +141,7 @@ protected function findTransitions(Definition $definition, bool $withMetadata): /** * @internal */ - protected function addPlaces(array $places, float $withMetadata): string + protected function addPlaces(array $places, bool $withMetadata): string { $code = ''; @@ -303,7 +303,7 @@ protected function addAttributes(array $attributes): string * * @internal */ - protected function formatLabel(Definition $definition, string $withMetadata, array $options): string + protected function formatLabel(Definition $definition, bool $withMetadata, array $options): string { $currentLabel = $options['label'] ?? ''; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy