8000 [Workflow] Introducing the workflow component by lyrixx · Pull Request #11882 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Workflow] Introducing the workflow component #11882

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"symfony/validator": "self.version",
"symfony/var-dumper": "self.version",
"symfony/web-profiler-bundle": "self.version",
"symfony/workflow": "self.version",
"symfony/yaml": "self.version"
},
"require-dev": {
Expand Down
52 changes: 52 additions & 0 deletions src/Symfony/Bridge/Twig/Extension/WorkflowExtension.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bridge\Twig\Extension;

use Symfony\Component\Workflow\Registry;

/**
* WorkflowExtension.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class WorkflowExtension extends \Twig_Extension
{
private $workflowRegistry;

public function __construct(Registry $workflowRegistry)
{
$this->workflowRegistry = $workflowRegistry;
}

public function getFunctions()
{
return array(
new \Twig_SimpleFunction('workflow_can', ar 6D47 ray($this, 'canTransition')),
new \Twig_SimpleFunction('workflow_transitions', array($this, 'getEnabledTransitions')),
);
}

public function canTransition($object, $transition, $name = null)
{
return $this->workflowRegistry->get($object, $name)->can($object, $transition);
}

public function getEnabledTransitions($object, $name = null)
{
return $this->workflowRegistry->get($object, $name)->getEnabledTransitions($object);
}

public function getName()
{
return 'workflow';
}
}
78 changes: 78 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/WorkflowDumpCommand.php
9E81
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Command;

use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Workflow\Dumper\GraphvizDumper;
use Symfony\Component\Workflow\Marking;

/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class WorkflowDumpCommand extends ContainerAwareCommand
{
public function isEnabled()
{
return $this->getContainer()->has('workflow.registry');
}

/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you implement isEnabled command to return false if you don't have any declared workflows ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I store nothing in the DIC about existing workflow. So I will not be able to do that with ease. I think I should add a parameter in the DIC for that.

So my question is: Do you really need this "feature" ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you access configuration from container like you did in the extension class in the Framework extension class ? not that easy indeed, I like your parameter injection idea 👍

Because we already have a lot of commands and for now, no way to filter except using this function, I think we got a chance to avoid this command when we don't need it.

To sum up:

  • if we have a workflow configurated => of course the command should be displayed: great !
  • if no workflow configurated for this application => does it make sense to display this command.. probably not.

What do you think ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes ; I agree with you. I will implement that ASAP.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, in a better way.

* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('workflow:dump')
->setDefinition(array(
new InputArgument('name', InputArgument::REQUIRED, 'A workflow name'),
new InputArgument('marking', InputArgument::IS_ARRAY, 'A marking (a list of places)'),
))
->setDescription('Dump a workflow')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command dumps the graphical representation of a
workflow in DOT format

%command.full_name% <workflow name> | dot -Tpng > workflow.png

EOF
)
;
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$workflow = $this->getContainer()->get('workflow.'.$input->getArgument('name'));
$definition = $this->getProperty($workflow, 'definition');

$dumper = new GraphvizDumper();

$marking = new Marking();
foreach ($input->getArgument('marking') as $place) {
$marking->mark($place);
}

$output->writeln($dumper->dump($definition, $marking));
}

private function getProperty($object, $property)
{
$reflectionProperty = new \ReflectionProperty(get_class($object), $property);
$reflectionProperty->setAccessible(true);

return $reflectionProperty->getValue($object);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public function getConfigTreeBuilder()
$this->addSsiSection($rootNode);
$this->addFragmentsSection($rootNode);
$this->addProfilerSection($rootNode);
$this->addWorkflowSection($rootNode);
$this->addRouterSection($rootNode);
$this->addSessionSection($rootNode);
$this->addRequestSection($rootNode);
Expand Down Expand Up @@ -226,6 +227,99 @@ private function addProfilerSection(ArrayNodeDefinition $rootNode)
;
}

private function addWorkflowSection(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('workflows')
->useAttributeAsKey('name')
->prototype('array')
->children()
->arrayNode('marking_store')
->isRequired()
->children()
->enumNode('type')
->values(array('property_accessor', 'scalar'))
->end()
->arrayNode('arguments')
->beforeNormalization()
->ifString()
->then(function ($v) { return array($v); })
->end()
->prototype('scalar')
->end()
->end()
->scalarNode('service')
->cannotBeEmpty()
->end()
->end()
->validate()
->always(function ($v) {
if (isset($v['type']) && isset($v['service'])) {
throw new \InvalidArgumentException('"type" and "service" could not be used together.');
}

return $v;
})
->end()
->end()
->arrayNode('supports')
->isRequired()
->beforeNormalization()
->ifString()
->then(function ($v) { return array($v); })
->end()
->prototype('scalar')
->cannotBeEmpty()
->validate()
->ifTrue(function ($v) { return !class_exists($v); })
->thenInvalid('The supported class %s does not exist.')
->end()
->end()
->end()
->arrayNode('places')
->isRequired()
->requiresAtLeastOneElement()
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->arrayNode('transitions')
->useAttributeAsKey('name')
->isRequired()
->requiresAtLeastOneElement()
->prototype('array')
->children()
->arrayNode('from')
->beforeNormalization()
->ifString()
->then(function ($v) { return array($v); })
->end()
->requiresAtLeastOneElement()
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->arrayNode('to')
->beforeNormalization()
->ifString()
->then(function ($v) { return array($v); })
->end()
->requiresAtLeastOneElement()
->prototype('scalar')
->cannotBeEmpty()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
->end()
;
}

private function addRouterSection(ArrayNodeDefinition $rootNode)
{
$rootNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Workflow;

/**
* FrameworkExtension.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jeremy Mikola <jmikola@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class FrameworkExtension extends Extension
{
Expand Down Expand Up @@ -129,6 +131,7 @@ public function load(array $configs, ContainerBuilder $container)
$this->registerTranslatorConfiguration($config['translator'], $container);
$this->registerProfilerConfiguration($config['profiler'], $container, $loader);
$this->registerCacheConfiguration($config['cache'], $container);
$this->registerWorkflowConfiguration($config['workflows'], $container, $loader);

if ($this->isConfigEnabled($container, $config['router'])) {
$this->registerRouterConfiguration($config['router'], $container, $loader);
Expand Down Expand Up @@ -346,6 +349,54 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
}
}

/**
* Loads the workflow configuration.
*
* @param array $workflows A workflow configuration array
* @param ContainerBuilder $container A ContainerBuilder instance
* @param XmlFileLoader $loader An XmlFileLoader instance
*/
private function registerWorkflowConfiguration(array $workflows, ContainerBuilder $container, XmlFileLoader $loader)
{
if (!$workflows) {
return;
}

$loader->load('workflow.xml');

$registryDefinition = $container->getDefinition('workflow.registry');

foreach ($workflows as $name => $workflow) {
$definitionDefinition = new Definition(Workflow\Definition::class);
$definitionDefinition->addMethodCall('addPlaces', array($workflow['places']));
foreach ($workflow['transitions'] as $transitionName => F438 $transition) {
$definitionDefinition->addMethodCall('addTransition', array(new Definition(Workflow\Transition::class, array($transitionName, $transition['from'], $transition['to']))));
}

if (isset($workflow['marking_store']['type'])) {
$markingStoreDefinition = new DefinitionDecorator('workflow.marking_store.'.$workflow['marking_store']['type']);
foreach ($workflow['marking_store']['arguments'] as $argument) {
$markingStoreDefinition->addArgument($argument);
}
} else {
$markingStoreDefinition = new Reference($workflow['marking_store']['service']);
}

$workflowDefinition = new DefinitionDecorator('workflow.abstract');
$workflowDefinition->replaceArgument(0, $definitionDefinition);
$workflowDefinition->replaceArgument(1, $markingStoreDefinition);
$workflowDefinition->replaceArgument(3, $name);

$workflowId = 'workflow.'.$name;

$container->setDefinition($workflowId, $workflowDefinition);

foreach ($workflow['supports'] as $supportedClass) {
$registryDefinition->addMethodCall('add', array(new Reference($workflowId), $supportedClass));
}
}
}

/**
* Loads the router configuration.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<xsd:element name="serializer" type="serializer" minOccurs="0" maxOccurs="1" />
<xsd:element name="property-info" type="property_info" minOccurs="0" maxOccurs="1" />
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
<xsd:element name="workflows" type="workflows" minOccurs="0" maxOccurs="1" />
</xsd:all>

<xsd:attribute name="http-method-override" type="xsd:boolean" />
Expand Down Expand Up @@ -224,4 +225,42 @@
<xsd:attribute name="provider" type="xsd:string" />
<xsd:attribute name="clearer" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="workflows">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element name="workflow" type="workflow" />
</xsd:choice>
</xsd:complexType>

<xsd:complexType name="workflow">
<xsd:sequence>
<xsd:element name="marking-store" type="marking_store" />
<xsd:element name="supports" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="places" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="transitions" type="transitions" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>

<xsd:complexType name="marking_store">
<xsd:sequence>
<xsd:element name="type" type="xsd:string" minOccurs="0" maxOccurs="1" />
<xsd:element name="arguments" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="service" type="xsd:string" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="transitions">
<xsd:sequence>
<xsd:element name="transition" type="transition" />
</xsd:sequence>
</xsd:complexType>

<xsd:complexType name="transition">
<xsd:sequence>
<xsd:element name="from" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
<xsd:element name="to" type="xsd:string" minOccurs="1" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:schema>
Loading
0