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

Lines changed: 1 addition & 0 deletions
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": {
Lines changed: 52 additions & 0 deletions
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+
}
Lines changed: 78 additions & 0 deletions
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

Lines changed: 94 additions & 0 deletions
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

Lines changed: 51 additions & 0 deletions
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

Lines changed: 39 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy