Skip to content

Commit 2eedbca

Browse files
committed
[Workflow] Add a MetadataStore
1 parent b0facfe commit 2eedbca

28 files changed

+648
-75
lines changed

UPGRADE-4.1.md

+11
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,14 @@ Workflow
6666
* Deprecated the `add` method in favor of the `addWorkflow` method in `Workflow\Registry`.
6767
* Deprecated `SupportStrategyInterface` in favor of `WorkflowSupportStrategyInterface`.
6868
* Deprecated the class `ClassInstanceSupportStrategy` in favor of the class `InstanceOfSupportStrategy`.
69+
* Deprecated passing the workflow name as 4th parameter of `Event` constructor in favor of the workflow itself.
70+
* [BCBREAK] The configuration of a place in XML has changed:
71+
72+
before:
73+
74+
<framework:place>first</framework:place>
75+
76+
77+
after:
78+
79+
<framework:place name="first" />

src/Symfony/Bridge/Twig/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
4.1.0
5+
-----
6+
7+
* add a `workflow_metadata` function
8+
49
3.4.0
510
-----
611

src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php

+21
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function getFunctions()
3737
new TwigFunction('workflow_transitions', array($this, 'getEnabledTransitions')),
3838
new TwigFunction('workflow_has_marked_place', array($this, 'hasMarkedPlace')),
3939
new TwigFunction('workflow_marked_places', array($this, 'getMarkedPlaces')),
40+
new TwigFunction('workflow_metadata', array($this, 'getMetadata')),
4041
);
4142
}
4243

@@ -101,6 +102,26 @@ public function getMarkedPlaces($subject, $placesNameOnly = true, $name = null)
101102
return $places;
102103
}
103104

105+
/**
106+
* Returns the metadata for a specific subject.
107+
*
108+
* @param object $subject A subject
109+
* @param string $key A key
110+
* @param null|string|Transition $metadataSubject Use null to get workflow metadata
111+
* Use a string (the place name) to get place metadata
112+
* Use a Transition instance to get transition metadata
113+
* @param string $name A workflow name
114+
*/
115+
public function getMetadata($subject, string $key, $metadataSubject = null, $name = null)
116+
{
117+
return $this
118+
->workflowRegistry
119+
->get($subject, $name)
120+
->getMetadataStore()
121+
->getMetadata($key, $metadataSubject)
122+
;
123+
}
124+
104125
public function getName()
105126
{
106127
return 'workflow';

src/Symfony/Bridge/Twig/Tests/Extension/WorkflowExtensionTest.php

+29-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use PHPUnit\Framework\TestCase;
1515
use Symfony\Bridge\Twig\Extension\WorkflowExtension;
1616
use Symfony\Component\Workflow\Definition;
17+
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
18+
use Symfony\Component\Workflow\Metadata\MetadataBag;
1719
use Symfony\Component\Workflow\Registry;
1820
use Symfony\Component\Workflow\SupportStrategy\ClassInstanceSupportStrategy;
1921
use Symfony\Component\Workflow\SupportStrategy\InstanceOfSupportStrategy;
@@ -23,6 +25,7 @@
2325
class WorkflowExtensionTest extends TestCase
2426
{
2527
private $extension;
28+
private $t1;
2629

2730
protected function setUp()
2831
{
@@ -32,10 +35,21 @@ protected function setUp()
3235

3336
$places = array('ordered', 'waiting_for_payment', 'processed');
3437
$transitions = array(
35-
new Transition('t1', 'ordered', 'waiting_for_payment'),
38+
$this->t1 = new Transition('t1', 'ordered', 'waiting_for_payment'),
3639
new Transition('t2', 'waiting_for_payment', 'processed'),
3740
);
38-
$definition = new Definition($places, $transitions);
41+
$transitionsMetadata = new \SplObjectStorage();
42+
$transitionsMetadata->attach($this->t1, new MetadataBag(array('title' => 't1 title')));
43+
44+
$metadataStore = null;
45+
if (class_exists(InMemoryMetadataStore::class)) {
46+
$metadataStore = new InMemoryMetadataStore(
47+
new MetadataBag(array('title' => 'workflow title')),
48+
array('orderer' => new MetadataBag(array('title' => 'ordered title'))),
49+
$transitionsMetadata
50+
);
51+
}
52+
$definition = new Definition($places, $transitions, null, $metadataStore);
3953
$workflow = new Workflow($definition);
4054

4155
$registry = new Registry();
@@ -88,4 +102,17 @@ public function testGetMarkedPlaces()
88102
$this->assertSame(array('ordered', 'waiting_for_payment'), $this->extension->getMarkedPlaces($subject));
89103
$this->assertSame($subject->marking, $this->extension->getMarkedPlaces($subject, false));
90104
}
105+
106+
public function testGetMetadata()
107+
{
108+
if (!class_exists(InMemoryMetadataStore::class)) {
109+
$this->markTestSkipped('This test requires symfony/component:4.1.');
110+
}
111+
$subject = new \stdClass();
112+
$subject->marking = array();
113+
114+
$this->assertSame('workflow title', $this->extension->getMetadata($subject, 'title'));
115+
$this->assertSame('ordered title', $this->extension->getMetadata($subject, 'title', 'orderer'));
116+
$this->assertSame('t1 title', $this->extension->getMetadata($subject, 'title', $this->t1));
117+
}
91118
}

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

+57-4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
* FrameworkExtension configuration structure.
3232
*
3333
* @author Jeremy Mikola <jmikola@gmail.com>
34+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
3435
*/
3536
class Configuration implements ConfigurationInterface
3637
{
@@ -292,23 +293,61 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
292293
->defaultNull()
293294
->end()
294295
->arrayNode('places')
296+
->beforeNormalization()
297+
->always()
298+
->then(function ($places) {
299+
// It's an indexed array, BC Layer
300+
if (isset($places[0]) && is_string($places[0])) {
301+
return array_map(function (string $place) {
302+
return array('name' => $place);
303+
}, $places);
304+
}
305+
306+
// It's an indexed array, we let the validation occurs
307+
if (isset($places[0]) && is_array($places[0])) {
308+
return $places;
309+
}
310+
311+
foreach ($places as $name => $place) {
312+
if (is_array($place) && array_key_exists('name', $place)) {
313+
continue;
314+
}
315+
$place['name'] = $name;
316+
$places[$name] = $place;
317+
}
318+
319+
return array_values($places);
320+
})
321+
->end()
295322
->isRequired()
296323
->requiresAtLeastOneElement()
297-
->prototype('scalar')
298-
->cannotBeEmpty()
324+
->prototype('array')
325+
->children()
326+
->scalarNode('name')
327+
->isRequired()
328+
->cannotBeEmpty()
329+
->end()
330+
->arrayNode('metadata')
331+
->normalizeKeys(false)
332+
->defaultValue(array())
333+
->example(array('color' => 'blue', 'description' => 'Workflow to manage article.'))
334+
->prototype('variable')
335+
->end()
336+
->end()
337+
->end()
299338
->end()
300339
->end()
301340
->arrayNode('transitions')
302341
->beforeNormalization()
303342
->always()
304343
->then(function ($transitions) {
305344
// It's an indexed array, we let the validation occurs
306-
if (isset($transitions[0])) {
345+
if (isset($transitions[0]) && is_array($transitions[0])) {
307346
return $transitions;
308347
}
309348

310349
foreach ($transitions as $name => $transition) {
311-
if (array_key_exists('name', $transition)) {
350+
if (is_array($transition) && array_key_exists('name', $transition)) {
312351
continue;
313352
}
314353
$transition['name'] = $name;
@@ -351,9 +390,23 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode)
351390
->cannotBeEmpty()
352391
->end()
353392
->end()
393+
->arrayNode('metadata')
394+
->normalizeKeys(false)
395+
->defaultValue(array())
396+
->example(array('color' => 'blue', 'description' => 'Workflow to manage article.'))
397+
->prototype('variable')
398+
->end()
399+
->end()
354400
->end()
355401
->end()
356402
->end()
403+
->arrayNode('metadata')
404+
->normalizeKeys(false)
405+
->defaultValue(array())
406+
->example(array('color' => 'blue', 'description' => 'Workflow to manage article.'))
407+
->prototype('variable')
408+
->end()
409+
->end()
357410
->end()
358411
->validate()
359412
->ifTrue(function ($v) {

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

+42-6
Original file line numberDiff line numberDiff line change
@@ -463,32 +463,68 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $
463463
foreach ($config['workflows'] as $name => $workflow) {
464464
$type = $workflow['type'];
465465

466+
// Process Metadata (workflow + places (transition is done in the "create transition" block))
467+
$metadataStoreDefinition = new Definition(Workflow\Metadata\InMemoryMetadataStore::class, array(null, null, null));
468+
if ($workflow['metadata']) {
469+
$metadataStoreDefinition->replaceArgument(0, new Definition(Workflow\Metadata\MetadataBag::class, array($workflow['metadata'])));
470+
}
471+
$placesMetadata = array();
472+
foreach ($workflow['places'] as $place) {
473+
if ($place['metadata']) {
474+
$placesMetadata[$place['name']] = new Definition(Workflow\Metadata\MetadataBag::class, array($place['metadata']));
475+
}
476+
}
477+
if ($placesMetadata) {
478+
$metadataStoreDefinition->replaceArgument(1, $placesMetadata);
479+
}
480+
481+
// Create transitions
466482
$transitions = array();
483+
$transitionsMetadataDefinition = new Definition(\SplObjectStorage::class);
467484
foreach ($workflow['transitions'] as $transition) {
468485
if ('workflow' === $type) {
469-
$transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to']));
486+
$transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $transition['from'], $transition['to']));
487+
$transitions[] = $transitionDefinition;
488+
if ($transition['metadata']) {
489+
$transitionsMetadataDefinition->addMethodCall('attach', array(
490+
$transitionDefinition,
491+
new Definition(Workflow\Metadata\MetadataBag::class, array($transition['metadata'])),
492+
));
493+
}
470494
} elseif ('state_machine' === $type) {
471495
foreach ($transition['from'] as $from) {
472496
foreach ($transition['to'] as $to) {
473-
$transitions[] = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to));
497+
$transitionDefinition = new Definition(Workflow\Transition::class, array($transition['name'], $from, $to));
498+
$transitions[] = $transitionDefinition;
499+
if ($transition['metadata']) {
500+
$transitionsMetadataDefinition->addMethodCall('attach', array(
501+
$transitionDefinition,
502+
new Definition(Workflow\Metadata\MetadataBag::class, array($transition['metadata'])),
503+
));
504+
}
474505
}
475506
}
476507
}
477508
}
509+
$metadataStoreDefinition->replaceArgument(2, $transitionsMetadataDefinition);
510+
511+
// Create places
512+
$places = array_map(function (array $place) {
513+
return $place['name'];
514+
}, $workflow['places']);
478515

479516
// Create a Definition
480517
$definitionDefinition = new Definition(Workflow\Definition::class);
481518
$definitionDefinition->setPublic(false);
482-
$definitionDefinition->addArgument($workflow['places']);
519+
$definitionDefinition->addArgument($places);
483520
$definitionDefinition->addArgument($transitions);
521+
$definitionDefinition->addArgument($workflow['initial_place'] ?? null);
522+
$definitionDefinition->addArgument($metadataStoreDefinition);
484523
$definitionDefinition->addTag('workflow.definition', array(
485524
'name' => $name,
486525
'type' => $type,
487526
'marking_store' => isset($workflow['marking_store']['type']) ? $workflow['marking_store']['type'] : null,
488527
));
489-
if (isset($workflow['initial_place'])) {
490-
$definitionDefinition->addArgument($workflow['initial_place']);
491-
}
492528

493529
// Create MarkingStore
494530
if (isset($workflow['marking_store']['type'])) {

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

+16-1
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,9 @@
271271
<xsd:sequence>
272272
<xsd:element name="marking-store" type="marking_store" minOccurs="0" maxOccurs="1" />
273273
<xsd:element name="support" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
274-
<xsd:element name="place" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
274+
<xsd:element name="place" type="place" minOccurs="0" maxOccurs="unbounded" />
275275
<xsd:element name="transition" type="transition" minOccurs="0" maxOccurs="unbounded" />
276+
<xsd:element name="metadata" type="metadata" minOccurs="0" maxOccurs="unbounded" />
276277
</xsd:sequence>
277278
<xsd:attribute name="name" type="xsd:string" />
278279
<xsd:attribute name="type" type="workflow_type" />
@@ -300,10 +301,24 @@
300301
<xsd:sequence>
301302
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
302303
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
304+
<xsd:element name="metadata" type="metadata" minOccurs="0" maxOccurs="unbounded" />
303305
</xsd:sequence>
304306
<xsd:attribute name="name" type="xsd:string" use="required" />
305307
</xsd:complexType>
306308

309+
<xsd:complexType name="place">
310+
<xsd:sequence>
311+
<xsd:element name="metadata" type="metadata" minOccurs="0" maxOccurs="unbounded" />
312+
</xsd:sequence>
313+
<xsd:attribute name="name" type="xsd:string" use="required" />
314+
</xsd:complexType>
315+
316+
<xsd:complexType name="metadata">
317+
<xsd:sequence>
318+
<xsd:any minOccurs="0" processContents="lax"/>
319+
</xsd:sequence>
320+
</xsd:complexType>
321+
307322
<xsd:simpleType name="workflow_type">
308323
<xsd:restriction base="xsd:string">
309324
<xsd:enumeration value="state_machine" />

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

+19-8
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,29 @@
4848
FrameworkExtensionTest::class,
4949
),
5050
'initial_place' => 'start',
51+
'metadata' => array(
52+
'title' => 'workflow title',
53+
),
5154
'places' => array(
52-
'start',
53-
'coding',
54-
'travis',
55-
'review',
56-
'merged',
57-
'closed',
55+
'start_name_not_used' => array(
56+
'name' => 'start',
57+
'metadata' => array(
58+
'title' => 'place start title',
59+
),
60+
),
61+
'coding' => null,
62+
'travis' => null,
63+
'review' => null,
64+
'merged' => null,
65+
'closed' => null,
5866
),
5967
'transitions' => array(
6068
'submit' => array(
6169
'from' => 'start',
6270
'to' => 'travis',
71+
'metadata' => array(
72+
'title' => 'transition submit title',
73+
),
6374
),
6475
'update' => array(
6576
'from' => array('coding', 'travis', 'review'),
@@ -96,8 +107,8 @@
96107
FrameworkExtensionTest::class,
97108
),
98109
'places' => array(
99-
'first',
100-
'last',
110+
array('name' => 'first'),
111+
array('name' => 'last'),
101112
),
102113
'transitions' => array(
103114
'go' => array(

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/workflow_with_arguments_and_service.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<framework:argument>a</framework:argument>
1414
</framework:marking-store>
1515
<framework:support>Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\FrameworkExtensionTest</framework:support>
16-
<framework:place>first</framework:place>
17-
<framework:place>last</framework:place>
16+
<framework:place name="first" />
17+
<framework:place name="last" />
1818
<framework:transition name="foobar">
1919
<framework:from>a</framework:from>
2020
<framework:to>a</framework:to>

0 commit comments

Comments
 (0)