Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit becb816

Browse filesBrowse files
committed
[Workflow] Add support for executing custom workflow definition validators during the container compilation
1 parent d5b5581 commit becb816
Copy full SHA for becb816

File tree

14 files changed

+256
-29
lines changed
Filter options

14 files changed

+256
-29
lines changed

‎src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ CHANGELOG
2525
* Set `framework.rate_limiter.limiters.*.lock_factory` to `auto` by default
2626
* Deprecate `RateLimiterFactory` autowiring aliases, use `RateLimiterFactoryInterface` instead
2727
* Allow configuring compound rate limiters
28+
* Support executing custom workflow validators during container compilation
2829

2930
7.2
3031
---

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+27-8Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
use Symfony\Component\Validator\Validation;
5252
use Symfony\Component\Webhook\Controller\WebhookController;
5353
use Symfony\Component\WebLink\HttpHeaderSerializer;
54+
use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface;
5455
use Symfony\Component\Workflow\WorkflowEvents;
5556

5657
/**
@@ -402,6 +403,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
402403
->useAttributeAsKey('name')
403404
->prototype('array')
404405
->fixXmlConfig('support')
406+
->fixXmlConfig('definition_validator')
405407
->fixXmlConfig('place')
406408
->fixXmlConfig('transition')
407409
->fixXmlConfig('event_to_dispatch', 'events_to_dispatch')
@@ -431,11 +433,28 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
431433
->prototype('scalar')
432434
->cannotBeEmpty()
433435
->validate()
434-
->ifTrue(fn ($v) => !class_exists($v) && !interface_exists($v, false))
436+
->ifTrue(static fn ($v) => !class_exists($v) && !interface_exists($v, false))
435437
->thenInvalid('The supported class or interface "%s" does not exist.')
436438
->end()
437439
->end()
438440
->end()
441+
->arrayNode('definition_validators')
442+
->prototype('scalar')
443+
->cannotBeEmpty()
444+
->validate()
445+
->ifTrue(static fn ($v) => !class_exists($v))
446+
->thenInvalid('The validation class %s does not exist.')
447+
->end()
448+
->validate()
449+
->ifTrue(static fn ($v) => !is_a($v, DefinitionValidatorInterface::class, true))
450+
->thenInvalid(\sprintf('The validation class %%s is not an instance of "%s".', DefinitionValidatorInterface::class))
451+
->end()
452+
->validate()
453+
->ifTrue(static fn ($v) => 1 <= (new \ReflectionClass($v))->getConstructor()?->getNumberOfRequiredParameters())
454+
->thenInvalid('The validation class %s constructor must not have any arguments.')
455+
->end()
456+
->end()
457+
->end()
439458
->scalarNode('support_strategy')
440459
->cannotBeEmpty()
441460
->end()
@@ -447,7 +466,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
447466
->variableNode('events_to_dispatch')
448467
->defaultValue(null)
449468
->validate()
450-
->ifTrue(function ($v) {
469+
->ifTrue(static function ($v) {
451470
if (null === $v) {
452471
return false;
453472
}
@@ -474,14 +493,14 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
474493
->arrayNode('places')
475494
->beforeNormalization()
476495
->always()
477-
->then(function ($places) {
496+
->then(static function ($places) {
478497
if (!\is_array($places)) {
479498
throw new InvalidConfigurationException('The "places" option must be an array in workflow configuration.');
480499
}
481500

482501
// It's an indexed array of shape ['place1', 'place2']
483502
if (isset($places[0]) && \is_string($places[0])) {
484-
return array_map(function (string $place) {
503+
return array_map(static function (string $place) {
485504
return ['name' => $place];
486505
}, $places);
487506
}
@@ -521,7 +540,7 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
521540
->arrayNode('transitions')
522541
->beforeNormalization()
523542
->always()
524-
->then(function ($transitions) {
543+
->then(static function ($transitions) {
525544
if (!\is_array($transitions)) {
526545
throw new InvalidConfigurationException('The "transitions" option must be an array in workflow configuration.');
527546
}
@@ -588,20 +607,20 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode): void
588607
->end()
589608
->end()
590609
->validate()
591-
->ifTrue(function ($v) {
610+
->ifTrue(static function ($v) {
592611
return $v['supports'] && isset($v['support_strategy']);
593612
})
594613
->thenInvalid('"supports" and "support_strategy" cannot be used together.')
595614
->end()
596615
->validate()
597-
->ifTrue(function ($v) {
616+
->ifTrue(static function ($v) {
598617
return !$v['supports'] && !isset($v['support_strategy']);
599618
})
600619
->thenInvalid('"supports" or "support_strategy" should be configured.')
601620
->end()
602621
->beforeNormalization()
603622
->always()
604-
->then(function ($values) {
623+
->then(static function ($values) {
605624
// Special case to deal with XML when the user wants an empty array
606625
if (\array_key_exists('event_to_dispatch', $values) && null === $values['event_to_dispatch']) {
607626
$values['events_to_dispatch'] = [];

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+19-16Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,7 +1117,8 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
11171117
}
11181118
}
11191119
$metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition);
1120-
$container->setDefinition(\sprintf('%s.metadata_store', $workflowId), $metadataStoreDefinition);
1120+
$metadataStoreId = \sprintf('%s.metadata_store', $workflowId);
1121+
$container->setDefinition($metadataStoreId, $metadataStoreDefinition);
11211122

11221123
// Create places
11231124
$places = array_column($workflow['places'], 'name');
@@ -1128,7 +1129,8 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
11281129
$definitionDefinition->addArgument($places);
11291130
$definitionDefinition->addArgument($transitions);
11301131
$definitionDefinition->addArgument($initialMarking);
1131-
$definitionDefinition->addArgument(new Reference(\sprintf('%s.metadata_store', $workflowId)));
1132+
$definitionDefinition->addArgument(new Reference($metadataStoreId));
1133+
$definitionDefinitionId = \sprintf('%s.definition', $workflowId);
11321134

11331135
// Create MarkingStore
11341136
$markingStoreDefinition = null;
@@ -1142,14 +1144,26 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
11421144
$markingStoreDefinition = new Reference($workflow['marking_store']['service']);
11431145
}
11441146

1147+
// Validation
1148+
$workflow['definition_validators'][] = match ($workflow['type']) {
1149+
'state_machine' => Workflow\Validator\StateMachineValidator::class,
1150+
'workflow' => Workflow\Validator\WorkflowValidator::class,
1151+
default => throw new \LogicException(\sprintf('Invalid workflow type "%s".', $workflow['type'])),
1152+
};
1153+
11451154
// Create Workflow
11461155
$workflowDefinition = new ChildDefinition(\sprintf('%s.abstract', $type));
1147-
$workflowDefinition->replaceArgument(0, new Reference(\sprintf('%s.definition', $workflowId)));
1156+
$workflowDefinition->replaceArgument(0, new Reference($definitionDefinitionId));
11481157
$workflowDefinition->replaceArgument(1, $markingStoreDefinition);
11491158
$workflowDefinition->replaceArgument(3, $name);
11501159
$workflowDefinition->replaceArgument(4, $workflow['events_to_dispatch']);
11511160

1152-
$workflowDefinition->addTag('workflow', ['name' => $name, 'metadata' => $workflow['metadata']]);
1161+
$workflowDefinition->addTag('workflow', [
1162+
'name' => $name,
1163+
'metadata' => $workflow['metadata'],
1164+
'definition_validators' => $workflow['definition_validators'],
1165+
'definition_id' => $definitionDefinitionId,
1166+
]);
11531167
if ('workflow' === $type) {
11541168
$workflowDefinition->addTag('workflow.workflow', ['name' => $name]);
11551169
} elseif ('state_machine' === $type) {
@@ -1158,21 +1172,10 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
11581172

11591173
// Store to container
11601174
$container->setDefinition($workflowId, $workflowDefinition);
1161-
$container->setDefinition(\sprintf('%s.definition', $workflowId), $definitionDefinition);
1175+
$container->setDefinition($definitionDefinitionId, $definitionDefinition);
11621176
$container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name.'.'.$type);
11631177
$container->registerAliasForArgument($workflowId, WorkflowInterface::class, $name);
11641178

1165-
// Validate Workflow
1166-
if ('state_machine' === $workflow['type']) {
1167-
$validator = new Workflow\Validator\StateMachineValidator();
1168-
} else {
1169-
$validator = new Workflow\Validator\WorkflowValidator();
1170-
}
1171-
1172-
$trs = array_map(fn (Reference $ref): Workflow\Transition => $container->get((string) $ref), $transitions);
1173-
$realDefinition = new Workflow\Definition($places, $trs, $initialMarking);
1174-
$validator->validate($realDefinition, $name);
1175-
11761179
// Add workflow to Registry
11771180
if ($workflow['supports']) {
11781181
foreach ($workflow['supports'] as $supportedClassName) {

‎src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
use Symfony\Component\VarExporter\Internal\Registry;
7878
use Symfony\Component\Workflow\DependencyInjection\WorkflowDebugPass;
7979
use Symfony\Component\Workflow\DependencyInjection\WorkflowGuardListenerPass;
80+
use Symfony\Component\Workflow\DependencyInjection\WorkflowValidatorPass;
8081

8182
// Help opcache.preload discover always-needed symbols
8283
class_exists(ApcuAdapter::class);
@@ -173,6 +174,7 @@ public function build(ContainerBuilder $container): void
173174
$container->addCompilerPass(new CachePoolPrunerPass(), PassConfig::TYPE_AFTER_REMOVING);
174175
$this->addCompilerPassIfExists($container, FormPass::class);
175176
$this->addCompilerPassIfExists($container, WorkflowGuardListenerPass::class);
177+
$this->addCompilerPassIfExists($container, WorkflowValidatorPass::class);
176178
$container->addCompilerPass(new ResettableServicePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -32);
177179
$container->addCompilerPass(new RegisterLocaleAwareServicesPass());
178180
$container->addCompilerPass(new TestServiceContainerWeakRefPass(), PassConfig::TYPE_BEFORE_REMOVING, -32);

‎src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,7 @@
449449
<xsd:element name="initial-marking" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
450450
<xsd:element name="marking-store" type="marking_store" minOccurs="0" maxOccurs="1" />
451451
<xsd:element name="support" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
452+
<xsd:element name="definition-validator" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
452453
<xsd:element name="event-to-dispatch" type="event_to_dispatch" minOccurs="0" maxOccurs="unbounded" />
453454
<xsd:element name="place" type="place" minOccurs="0" maxOccurs="unbounded" />
454455
<xsd:element name="transition" type="transition" minOccurs="0" maxOccurs="unbounded" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator;
4+
5+
use Symfony\Component\Workflow\Definition;
6+
use Symfony\Component\Workflow\Validator\DefinitionValidatorInterface;
7+
8+
class DefinitionValidator implements DefinitionValidatorInterface
9+
{
10+
public static bool $called = false;
11+
12+
public function validate(Definition $definition, string $name): void
13+
{
14+
self::$called = true;
15+
}
16+
}

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/workflows.php
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
'supports' => [
1414
FrameworkExtensionTestCase::class,
1515
],
16+
'definition_validators' => [
17+
Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator\DefinitionValidator::class,
18+
],
1619
'initial_marking' => ['draft'],
1720
'metadata' => [
1821
'title' => 'article workflow',

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflows.xml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflows.xml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<framework:audit-trail enabled="true"/>
1414
<framework:initial-marking>draft</framework:initial-marking>
1515
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTestCase</framework:support>
16+
<framework:definition-validator>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator\DefinitionValidator</framework:definition-validator>
1617
<framework:place name="draft" />
1718
<framework:place name="wait_for_journalist" />
1819
<framework:place name="approved_by_journalist" />

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflows.yml

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/yml/workflows.yml
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ framework:
99
type: workflow
1010
supports:
1111
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTestCase
12+
definition_validators:
13+
- Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator\DefinitionValidator
1214
initial_marking: [draft]
1315
metadata:
1416
title: article workflow

‎src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTestCase.php
+10-2Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Psr\Log\LoggerAwareInterface;
1616
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
1717
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
18+
use Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Fixtures\Workflow\Validator\DefinitionValidator;
1819
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Messenger\DummyMessage;
1920
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
2021
use Symfony\Bundle\FullStack;
@@ -287,7 +288,11 @@ public function testProfilerCollectSerializerDataEnabled()
287288

288289
public function testWorkflows()
289290
{
290-
$container = $this->createContainerFromFile('workflows');
291+
DefinitionValidator::$called = false;
292+
293+
$container = $this->createContainerFromFile('workflows', compile: false);
294+
$container->addCompilerPass(new \Symfony\Component\Workflow\DependencyInjection\WorkflowValidatorPass());
295+
$container->compile();
291296

292297
$this->assertTrue($container->hasDefinition('workflow.article'), 'Workflow is registered as a service');
293298
$this->assertSame('workflow.abstract', $container->getDefinition('workflow.article')->getParent());
@@ -310,6 +315,7 @@ public function testWorkflows()
310315
], $tags['workflow'][0]['metadata'] ?? null);
311316

312317
$this->assertTrue($container->hasDefinition('workflow.article.definition'), 'Workflow definition is registered as a service');
318+
$this->assertTrue(DefinitionValidator::$called, 'DefinitionValidator is called');
313319

314320
$workflowDefinition = $container->getDefinition('workflow.article.definition');
315321

@@ -403,7 +409,9 @@ public function testWorkflowAreValidated()
403409
{
404410
$this->expectException(InvalidDefinitionException::class);
405411
$this->expectExceptionMessage('A transition from a place/state must have an unique name. Multiple transitions named "go" from place/state "first" were found on StateMachine "my_workflow".');
406-
$this->createContainerFromFile('workflow_not_valid');
412+
$container = $this->createContainerFromFile('workflow_not_valid', compile: false);
413+
$container->addCompilerPass(new \Symfony\Component\Workflow\DependencyInjection\WorkflowValidatorPass());
414+
$container->compile();
407415
}
408416

409417
public function testWorkflowCannotHaveBothSupportsAndSupportStrategy()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.