From d1fa644af5127ba3ba0560aa28f864e3359486da Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 23 May 2023 17:24:39 +0200 Subject: [PATCH 01/33] [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 3bd6c35a2f610f11e1bb299b3698618555a54187 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 4 Jul 2023 14:50:59 +0200 Subject: [PATCH 02/33] [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 03/33] [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 abae331bb94a0621683d8cee2668829166ecff24 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 21 Jul 2023 15:36:26 +0200 Subject: [PATCH 04/33] 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 83a806cf5ba6c997192ec1a7cc9591a854705e7f Mon Sep 17 00:00:00 2001 From: Hugo Hamon Date: Sat, 26 Aug 2023 13:27:34 +0200 Subject: [PATCH 05/33] 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 06/33] [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 3c34806f7b143a64ce8a12a93c93a7f1cd101256 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sat, 25 Nov 2023 21:15:12 +0100 Subject: [PATCH 07/33] 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 1a7ed3511fe247398de4cb4b7c4130823ecd6f2d Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Fri, 1 Dec 2023 16:42:06 +0100 Subject: [PATCH 08/33] [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 09/33] 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 f83393890ae88b2e8b5ee747921ab9a46e2aaaf1 Mon Sep 17 00:00:00 2001 From: Oskar Stark Date: Wed, 1 Nov 2023 09:14:07 +0100 Subject: [PATCH 10/33] [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 11/33] [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 12/33] 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 13/33] [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 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 14/33] [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 15/33] [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 16/33] 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 17/33] [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 18/33] 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 19/33] [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 6e7c3ad0a737049a47d834c44158f465c431b692 Mon Sep 17 00:00:00 2001 From: Christian Flothmann Date: Mon, 3 Jun 2024 15:27:28 +0200 Subject: [PATCH 20/33] 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 21/33] 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 22/33] 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 23/33] 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 24/33] 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 25/33] 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 26/33] [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 27/33] 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 74fffece2459b9cf4c542f76010a152a34421bbf Mon Sep 17 00:00:00 2001 From: Alexandre Daubois Date: Tue, 10 Dec 2024 09:51:17 +0100 Subject: [PATCH 28/33] [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 29/33] 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 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 30/33] [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 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 31/33] [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 32/33] 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 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 33/33] [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",