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' => [