8000 [Workflow] Added initial set of files · symfony/symfony@20eb0da · GitHub
[go: up one dir, main page]

Skip to content

Commit 20eb0da

Browse files
committed
[Workflow] Added initial set of files
1 parent 1298ce5 commit 20eb0da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+2523
-0
lines changed

composer.json

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"symfony/validator": "self.version",
7373
"symfony/var-dumper": "self.version",
7474
"symfony/web-profiler-bundle": "self.version",
75+
"symfony/workflow": "self.version",
7576
"symfony/yaml": "self.version"
7677
},
7778
"require-dev": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Twig\Extension;
13+
14+
use Symfony\Component\Workflow\Registry;
15+
16+
/**
17+
* WorkflowExtension.
18+
*
19+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
20+
*/
21+
class WorkflowExtension extends \Twig_Extension
22+
{
23+
private $workflowRegistry;
24+
25+
public function __construct(Registry $workflowRegistry)
26+
{
27+
$this->workflowRegistry = $workflowRegistry;
28+
}
29+
30+
public function getFunctions()
31+
{
32+
return array(
33+
new \Twig_SimpleFunction('workflow_can', array($this, 'canTransition')),
34+
new \Twig_SimpleFunction('workflow_transitions', array($this, 'getEnabledTransitions')),
35+
);
36+
}
37+
38+
public function canTransition($object, $transition, $name = null)
39+
{
40+
return $this->workflowRegistry->get($object, $name)->can($object, $transition);
41+
}
42+
43+
public function getEnabledTransitions($object, $name = null)
44+
{
45+
return $this->workflowRegistry->get($object, $name)->getEnabledTransitions($object);
46+
}
47+
48+
public function getName()
49+
{
50+
return 'workflow';
51+
}
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\FrameworkBundle\Command;
13+
14+
use Symfony\Component\Console\Input\InputArgument;
15+
use Symfony\Component\Console\Input\InputInterface;
16+
use Symfony\Component\Console\Output\OutputInterface;
17+
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
18+
use Symfony\Component\Workflow\Marking;
19+
20+
/**
21+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
22+
*/
23+
class WorkflowDumpCommand extends ContainerAwareCommand
24+
{
25+
public function isEnabled()
26+
{
27+
return $this->getContainer()->has('workflow.registry');
28+
}
29+
30+
/**
31+
* {@inheritdoc}
32+
*/
33+
protected function configure()
34+
{
35+
$this
36+
->setName('workflow:dump')
37+
->setDefinition(array(
38+
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
39+
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
40+
))
41+
->setDescription('Dump a workflow')
42+
->setHelp(<<<'EOF'
43+
The <info>%command.name%</info> command dumps the graphical representation of a
44+
workflow in DOT format
45+
46+
%command.full_name% <workflow name> | dot -Tpng > workflow.png
47+
48+
EOF
49+
)
50+
;
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
protected function execute(InputInterface $input, OutputInterface $output)
57+
{
58+
$workflow = $this->getContainer()->get('workflow.'.$input->getArgument('name'));
59+
$definition = $this->getProperty($workflow, 'definition');
60+
61+
$dumper = new GraphvizDumper();
62+
63+
$marking = new Marking();
64+
foreach ($input->getArgument('marking') as $place) {
65+
$marking->mark($place);
66+
}
67+
68+
$output->writeln($dumper->dump($definition, $marking));
69+
}
70+
71+
private function getProperty($object, $property)
72+
{
73+
$reflectionProperty = new \ReflectionProperty(get_class($object), $property);
74+
$reflectionProperty->setAccessible(true);
75+
76+
return $reflectionProperty->getValue($object);
77+
}
78+
}

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

+94
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public function getConfigTreeBuilder()
103103
$this->addSsiSection($rootNode);
104104
$this->addFragmentsSection($rootNode);
105105
$this->addProfilerSection($rootNode);
106+
$this->addWorkflowSection($rootNode);
106107
$this->addRouterSection($rootNode);
107108
$this->addSessionSection($rootNode);
108109
$this->addRequestSection($rootNode);
@@ -226,6 +227,99 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode)
226227
;
227228
}
228229

230+
private function addWorkflowSection(ArrayNodeDefinition $rootNode)
231+
{
232+
$rootNode
233+
->children()
234+
->arrayNode('workflows')
235+
->useAttributeAsKey('name')
236+
->prototype('array')
237+
->children()
238+
->arrayNode('marking_store')
239+
->isRequired()
240+
->children()
241+
->enumNode('type')
242+
->values(array('property_accessor', 'scalar'))
243+
->end()
244+
->arrayNode('arguments')
245+
->beforeNormalization()
246+
->ifString()
247+
->then(function ($v) { return array($v); })
248+
->end()
249+
->prototype('scalar')
250+
->end()
251+
->end()
252+
->scalarNode('service')
253+
->cannotBeEmpty()
254+
->end()
255+
->end()
256+
->validate()
257+
->always(function ($v) {
258+
if (isset($v['type']) && isset($v['service'])) {
259+
throw new \InvalidArgumentException('"type" and "service" could not be used together.');
260+
}
261+
262+
return $v;
263+
})
264+
->end()
265+
->end()
266+
->arrayNode('supports')
267+
->isRequired()
268+
->beforeNormalization()
269+
->ifString()
270+
->then(function ($v) { return array($v); })
271+
->end()
272+
->prototype('scalar')
273+
->cannotBeEmpty()
274+
->validate()
275+
->ifTrue(function ($v) { return !class_exists($v); })
276+
->thenInvalid('The supported class %s does not exist.')
277+
->end()
278+
->end()
279+
->end()
280+
->arrayNode('places')
281+
->isRequired()
282+
->requiresAtLeastOneElement()
283+
->prototype('scalar')
284+
->cannotBeEmpty()
285+
->end()
286+
->end()
287+
->arrayNode('transitions')
288+
->useAttributeAsKey('name')
289+
->isRequired()
290+
->requiresAtLeastOneElement()
291+
->prototype('array')
292+
->children()
293+
->arrayNode('from')
294+
->beforeNormalization()
295+
->ifString()
296+
->then(function ($v) { return array($v); })
297+
->end()
298+
->requiresAtLeastOneElement()
299+
->prototype('scalar')
300+
->cannotBeEmpty()
301+
->end()
302+
->end()
303+
->arrayNode('to')
304+
->beforeNormalization()
305+
->ifString()
306+
->then(function ($v) { return array($v); })
307+
->end()
308+
->requiresAtLeastOneElement()
309+
->prototype('scalar')
310+
->cannotBeEmpty()
311+
->end()
312+
->end()
313+
->end()
314+
->end()
315+
->end()
316+
->end()
317+
->end()
318+
->end()
319+
->end()
320+
;
321+
}
322+
229323
private function addRouterSection(ArrayNodeDefinition $rootNode)
230324
{
231325
$rootNode

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

+51
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
3232
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
3333
use Symfony\Component\Validator\Validation;
34+
use Symfony\Component\Workflow;
3435

3536
/**
3637
* FrameworkExtension.
3738
*
3839
* @author Fabien Potencier <fabien@symfony.com>
3940
* @author Jeremy Mikola <jmikola@gmail.com>
4041
* @author Kévin Dunglas <dunglas@gmail.com>
42+
* @author Grégoire Pineau <lyrixx@lyrixx.info>
4143
*/
4244
class FrameworkExtension extends Extension
4345
{
@@ -129,6 +131,7 @@ public function load(array $configs, ContainerBuilder $container)
129131
$this->registerTranslatorConfiguration($config['translator'], $container);
130132
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
131133
$this->registerCacheConfiguration($config['cache'], $container);
134+
$this->registerWorkflowConfiguration($config['workflows'], $container, $loader);
132135

133136
if ($this->isConfigEnabled($container, $config['router'])) {
134137
$this->registerRouterConfiguration($config['router'], $container, $loader);
@@ -346,6 +349,54 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
346349
}
347350
}
348351

352+
/**
353+
* Loads the workflow configuration.
354+
*
355+
* @param array $workflows A workflow configuration array
356+
* @param ContainerBuilder $container A ContainerBuilder instance
357+
* @param XmlFileLoader $loader An XmlFileLoader instance
358+
*/
359+
private function registerWorkflowConfiguration(array $workflows, ContainerBuilder $container, XmlFileLoader $loader)
360+
{
361+
if (!$workflows) {
362+
return;
363+
}
364+
365+
$loader->load('workflow.xml');
366+
367+
$registryDefinition = $container->getDefinition('workflow.registry');
368+
369+
foreach ($workflows as $name => $workflow) {
370+
$definitionDefinition = new Definition(Workflow\Definition::class);
371+
$definitionDefinition->addMethodCall('addPlaces', array($workflow['places']));
372+
foreach ($workflow['transitions'] as $transitionName => $transition) {
373+
$definitionDefinition->addMethodCall('addTransition', array(new Definition(Workflow\Transition::class, array($transitionName, $transition['from'], $transition['to']))));
374+
}
375+
376+
if (isset($workflow['marking_store']['type'])) {
377+
$markingStoreDefinition = new DefinitionDecorator('workflow.marking_store.'.$workflow['marking_store']['type']);
378+
foreach ($workflow['marking_store']['arguments'] as $argument) {
379+
$markingStoreDefinition->addArgument($argument);
380+
}
381+
} else {
382+
$markingStoreDefinition = new Reference($workflow['marking_store']['service']);
383+
}
384+
385+
$workflowDefinition = new DefinitionDecorator('workflow.abstract');
386+
$workflowDefinition->replaceArgument(0, $definitionDefinition);
387+
$workflowDefinition->replaceArgument(1, $markingStoreDefinition);
388+
$workflowDefinition->replaceArgument(3, $name);
389+
390+
$workflowId = 'workflow.'.$name;
391+
392+
$container->setDefinition($workflowId, $workflowDefinition);
393+
394+
foreach ($workflow['supports'] as $supportedClass) {
395+
$registryDefinition->addMethodCall('add', array(new Reference($workflowId), $supportedClass));
396+
}
397+
}
398+
}
399+
349400
/**
350401
* Loads the router configuration.
351402
*

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

+39
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
2727
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
2828
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
29+
<xsd:element name="workflows" type="workflows" minOccurs="0" maxOccurs="1" />
2930
</xsd:all>
3031

3132
<xsd:attribute name="http-method-override" type="xsd:boolean" />
@@ -224,4 +225,42 @@
224225
<xsd:attribute name="provider" type="xsd:string" />
225226
<xsd:attribute name="clearer" type="xsd:string" />
226227
</xsd:complexType>
228+
229+
<xsd:complexType name="workflows">
230+
<xsd:choice minOccurs="0" maxOccurs="unbounded">
231+
<xsd:element name="workflow" type="workflow" />
232+
</xsd:choice>
233+
</xsd:complexType>
234+
235+
<xsd:complexType name="workflow">
236+
<xsd:sequence>
237+
<xsd:element name="marking-store" type="marking_store" />
238+
<xsd:element name="supports" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
239+
<xsd:element name="places" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
240+
<xsd:element name="transitions" type="transitions" />
241+
</xsd:sequence>
242+
<xsd:attribute name="name" type="xsd:string" />
243+
</xsd:complexType>
244+
245+
<xsd:complexType name="marking_store">
246+
<xsd:sequence>
247+
<xsd:element name="type" type="xsd:string" minOccurs="0" maxOccurs="1" />
248+
<xsd:element name="arguments" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
249+
<xsd:element name="service" type="xsd:string" minOccurs="0" maxOccurs="1" />
250+
</xsd:sequence>
251+
</xsd:complexType>
252+
253+
<xsd:complexType name="transitions">
254+
<xsd:sequence>
255+
<xsd:element name="transition" type="transition" />
256+
</xsd:sequence>
257+
</xsd:complexType>
258+
259+
<xsd:complexType name="transition">
260+
<xsd:sequence>
261+
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
262+
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
263+
</xsd:sequence>
264+
<xsd:attribute name="name" type="xsd:string" />
265+
</xsd:complexType>
227266
</xsd:schema>

0 commit comments

Comments
 (0)
0