diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index b41bfd546e2a7..e322e32b8d4fc 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -12,6 +12,7 @@ CHANGELOG * Add `framework.type_info.aliases` option * Add `KernelBrowser::getSession()` * Add autoconfiguration tag `kernel.uri_signer` to `Symfony\Component\HttpFoundation\UriSigner` + * Add support for configuring workflow places with glob patterns matching consts/backed enums 7.3 --- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index e9ebc7695c918..4b23203af22c9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -26,6 +26,7 @@ use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Exception\LogicException; +use Symfony\Component\Finder\Glob; use Symfony\Component\Form\Form; use Symfony\Component\HtmlSanitizer\HtmlSanitizerInterface; use Symfony\Component\HttpClient\HttpClient; @@ -364,7 +365,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->arrayNode('workflows', 'workflow') ->canBeEnabled() ->beforeNormalization() - ->always(function ($v) { + ->always(static function ($v) { if (\is_array($v) && true === $v['enabled']) { $workflows = $v; unset($workflows['enabled']); @@ -478,15 +479,36 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->end() ->arrayNode('places', 'place') ->beforeNormalization() - ->always() - ->then(static function ($places) { - if (!\is_array($places)) { - throw new InvalidConfigurationException('The "places" option must be an array in workflow configuration.'); + ->always(static function ($places) { + if (\is_string($places)) { + if (2 !== \count($places = explode('::', $places, 2))) { + throw new InvalidConfigurationException('The "places" option must be a "FQCN::glob" pattern in workflow configuration.'); + } + [$class, $pattern] = $places; + if (!class_exists($class) && !interface_exists($class, false)) { + throw new InvalidConfigurationException(\sprintf('The "places" option must be a "FQCN::glob" pattern in workflow configuration, but class "%s" is not found.', $class)); + } + + $places = []; + $regex = Glob::toRegex($pattern, false); + + foreach ((new \ReflectionClass($class))->getConstants() as $name => $value) { + if (preg_match($regex, $name)) { + $places[] = $value; + } + } + if (!$places) { + throw new InvalidConfigurationException(\sprintf('No places found for pattern "%s::%s" in workflow configuration.', $class, $pattern)); + } + } elseif (!\is_array($places)) { + throw new InvalidConfigurationException('The "places" option must be an array or a "FQCN::glob" pattern in workflow configuration.'); } $normalizedPlaces = []; foreach ($places as $key => $value) { - if (!\is_array($value)) { + if ($value instanceof \BackedEnum) { + $value = ['name' => $value->value]; + } elseif (!\is_array($value)) { $value = ['name' => $value]; } $value['name'] ??= $key; @@ -514,8 +536,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->end() ->arrayNode('transitions', 'transition') ->beforeNormalization() - ->always() - ->then(static function ($transitions) { + ->always(static function ($transitions) { if (!\is_array($transitions)) { throw new InvalidConfigurationException('The "transitions" option must be an array in workflow configuration.'); } @@ -550,14 +571,18 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->example('is_fully_authenticated() and is_granted(\'ROLE_JOURNALIST\') and subject.getTitle() == \'My first article\'') ->end() ->arrayNode('from') - ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->always(static fn ($from) => array_map(static fn ($v) => $v instanceof \BackedEnum ? $v->value : $v, \is_array($from) ? $from : [$from])) + ->end() ->requiresAtLeastOneElement() ->prototype('scalar') ->cannotBeEmpty() ->end() ->end() ->arrayNode('to') - ->beforeNormalization()->castToArray()->end() + ->beforeNormalization() + ->always(static fn ($to) => array_map(static fn ($v) => $v instanceof \BackedEnum ? $v->value : $v, \is_array($to) ? $to : [$to])) + ->end() ->requiresAtLeastOneElement() ->prototype('scalar') ->cannotBeEmpty() @@ -582,28 +607,23 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void ->end() ->end() ->validate() - ->ifTrue(static function ($v) { - return $v['supports'] && isset($v['support_strategy']); - }) + ->ifTrue(static fn ($v) => $v['supports'] && isset($v['support_strategy'])) ->thenInvalid('"supports" and "support_strategy" cannot be used together.') ->end() ->validate() - ->ifTrue(static function ($v) { - return !$v['supports'] && !isset($v['support_strategy']); - }) + ->ifTrue(static fn ($v) => !$v['supports'] && !isset($v['support_strategy'])) ->thenInvalid('"supports" or "support_strategy" should be configured.') ->end() ->beforeNormalization() - ->always() - ->then(static function ($values) { - // Special case to deal with XML when the user wants an empty array - if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { - $values['events_to_dispatch'] = []; - unset($values['event_to_dispatch']); - } + ->always(static function ($values) { + // Special case to deal with XML when the user wants an empty array + if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) { + $values['events_to_dispatch'] = []; + unset($values['event_to_dispatch']); + } - return $values; - }) + return $values; + }) ->end() ->end() ->end() diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 24953f8ee8600..a94430b73f1c1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -475,6 +475,7 @@ + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/Workflow/Places.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/Workflow/Places.php new file mode 100644 index 0000000000000..47a6b5a4c0c73 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/Workflow/Places.php @@ -0,0 +1,10 @@ +loadFromExtension('framework', [ + 'workflows' => [ + 'enum' => [ + 'supports' => [ + FrameworkExtensionTestCase::class, + ], + 'places' => Places::cases(), + 'transitions' => [ + 'one' => [ + 'from' => Places::A->value, + 'to' => Places::B->value, + ], + 'two' => [ + 'from' => Places::B->value, + 'to' => Places::C->value, + ], + ], + ] + ], +]); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php index 2c29b848901eb..c8ca9ea8021da 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php @@ -1,5 +1,6 @@ loadFromExtension('framework', [ diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_enum.xml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_enum.xml new file mode 100644 index 0000000000000..f5d198b5b5c37 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_enum.xml @@ -0,0 +1,23 @@ + + + + + + + + Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTestCase + + a + b + + + b + c + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_enum.yml b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_enum.yml new file mode 100644 index 0000000000000..d72eab0e3ac64 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflow_enum.yml @@ -0,0 +1,13 @@ +framework: + workflows: + enum: + supports: + - Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTestCase + places: !php/enum Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Places + transitions: + one: + from: !php/enum Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Places::A + to: !php/enum Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Places::B + two: + from: !php/enum Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Places::B + to: !php/enum Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Places::C diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php index 5c732b188fbd5..f5f1008cae16a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php @@ -519,6 +519,14 @@ public function testWorkflowMultipleTransitionsWithSameName() ], $container->getDefinition($transitions[4])->getArguments()); } + public function testWorkflowEnum() + { + $container = $this->createContainerFromFile('workflow_enum'); + + $workflowDefinition = $container->getDefinition('state_machine.enum.definition'); + $this->assertSame(['a', 'b', 'c'], $workflowDefinition->getArgument(0)); + } + public function testWorkflowGuardExpressions() { $container = $this->createContainerFromFile('workflow_with_guard_expression'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php index d3c1f8ef4bfe1..92a5fd74b6e13 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/PhpFrameworkExtensionTest.php @@ -75,7 +75,7 @@ public function testAssetPackageCannotHavePathAndUrl() public function testWorkflowValidationPlacesIsArray() { $this->expectException(InvalidConfigurationException::class); - $this->expectExceptionMessage('The "places" option must be an array in workflow configuration.'); + $this->expectExceptionMessage('The "places" option must be an array or a "FQCN::glob" pattern in workflow configuration.'); $this->createContainerFromClosure(function ($container) { $container->loadFromExtension('framework', [ 'workflows' => [