diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 1766366252147..27ef7913e2c1f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -244,6 +244,12 @@ private function addWorkflowSection(ArrayNodeDefinition $rootNode) ->arrayNode('audit_trail') ->canBeEnabled() ->end() + ->arrayNode('marking_history_store') + ->children() + ->scalarNode('history_property')->isRequired()->end() + ->scalarNode('memo_property')->end() + ->end() + ->end() ->enumNode('type') ->values(array('workflow', 'state_machine')) ->defaultValue('state_machine') diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 1321d7126c899..a488e88d0b66a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -572,6 +572,17 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $markingStoreDefinition = new Reference($workflow['marking_store']['service']); } + // create Marking History Store for optionally logging state history + if (isset($workflow['marking_history_store'])) { + $historyProperty = $workflow['marking_history_store']['history_property']; + $memoProperty = $workflow['marking_history_store']['memo_property']; + + $markingHistoryDefinition = new Definition(Workflow\MarkingHistoryStore::class); + $markingHistoryDefinition->setPublic(false); + $markingHistoryDefinition->addArgument($historyProperty); + $markingHistoryDefinition->addArgument($memoProperty); + } + // Create Workflow $workflowId = sprintf('%s.%s', $type, $name); $workflowDefinition = new ChildDefinition(sprintf('%s.abstract', $type)); @@ -580,6 +591,9 @@ private function registerWorkflowConfiguration(array $config, ContainerBuilder $ $workflowDefinition->replaceArgument(1, $markingStoreDefinition); } $workflowDefinition->replaceArgument(3, $name); + if (isset($markingHistoryDefinition)) { + $workflowDefinition->replaceArgument(4, $markingHistoryDefinition); + } // Store to container $container->setDefinition($workflowId, $workflowDefinition); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml index d881f699f75ec..e0a2060c3babf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/workflow.xml @@ -12,17 +12,21 @@ null + null null + null + + diff --git a/src/Symfony/Component/Workflow/MarkingHistoryStore.php b/src/Symfony/Component/Workflow/MarkingHistoryStore.php new file mode 100644 index 0000000000000..a638fafb6fde7 --- /dev/null +++ b/src/Symfony/Component/Workflow/MarkingHistoryStore.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Workflow; + +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +final class MarkingHistoryStore +{ + private $historyProperty; + private $memoProperty; + + private $historyPropertyAccessor; + private $memoPropertyAccessor; + + public function __construct(string $historyProperty, string $memoProperty = null, PropertyAccessorInterface $historyPropertyAccessor = null, PropertyAccessorInterface $memoPropertyAccessor = null) + { + $this->historyProperty = $historyProperty; + $this->memoProperty = $memoProperty; + + $this->historyPropertyAccessor = $historyPropertyAccessor ?? PropertyAccess::createPropertyAccessor(); + $this->memoPropertyAccessor = $memoPropertyAccessor ?? PropertyAccess::createPropertyAccessor(); + } + + /** + * @param $subject + * @param $transition Transition + * @param $marking Marking + * @param $workflowName string + */ + public function updateMarkingHistory($subject, Transition $transition, Marking $marking, string $workflowName) + { + // get existing state history for this object + $existingHistory = $this->historyPropertyAccessor->getValue($subject, $this->historyProperty) ?? array(); + + // build the array to append to the log, using the workflow name as the log's key + $arr = array(); + $arr['timestamp'] = date('Y-m-d H:i:s'); + $arr['marking'] = $marking->getPlaces(); + $arr['transition'] = $transition->getName(); + + if (null !== $this->memoProperty) { + $arr['memo'] = $this->memoPropertyAccessor->getValue($subject, $this->memoProperty) ?? ''; // an optional memo + } + + // the key is used to allow logging the history of multiple workflows + $key = $workflowName; + if (!array_key_exists($key, $existingHistory)) { + $existingHistory[$key] = array(); + } + $existingHistory[$key][] = $arr; + + // set the history property value + $this->historyPropertyAccessor->setValue($subject, $this->historyProperty, $existingHistory); + + // finally, clear out the memo field now that it's been logged + $this->memoPropertyAccessor->setValue($subject, $this->memoProperty, null); + } +} diff --git a/src/Symfony/Component/Workflow/StateMachine.php b/src/Symfony/Component/Workflow/StateMachine.php index 6adf1f4dcbddb..7d5e7dfd56d99 100644 --- a/src/Symfony/Component/Workflow/StateMachine.php +++ b/src/Symfony/Component/Workflow/StateMachine.php @@ -1,5 +1,14 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Symfony\Component\Workflow; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -11,8 +20,8 @@ */ class StateMachine extends Workflow { - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') + public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', MarkingHistoryStore $markingHistoryStore = null) { - parent::__construct($definition, $markingStore ?: new SingleStateMarkingStore(), $dispatcher, $name); + parent::__construct($definition, $markingStore ?: new SingleStateMarkingStore(), $dispatcher, $name, $markingHistoryStore); } } diff --git a/src/Symfony/Component/Workflow/Workflow.php b/src/Symfony/Component/Workflow/Workflow.php index 963f1b8b27180..bcfbca1e7b242 100644 --- a/src/Symfony/Component/Workflow/Workflow.php +++ b/src/Symfony/Component/Workflow/Workflow.php @@ -32,13 +32,15 @@ class Workflow implements WorkflowInterface private $markingStore; private $dispatcher; private $name; + private $markingHistoryStore; - public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed') + public function __construct(Definition $definition, MarkingStoreInterface $markingStore = null, EventDispatcherInterface $dispatcher = null, string $name = 'unnamed', MarkingHistoryStore $markingHistoryStore = null) { $this->definition = $definition; $this->markingStore = $markingStore ?: new MultipleStateMarkingStore(); $this->dispatcher = $dispatcher; $this->name = $name; + $this->markingHistoryStore = $markingHistoryStore; } /** @@ -160,6 +162,10 @@ public function apply($subject, $transitionName) $this->markingStore->setMarking($subject, $marking); + if (null !== $this->markingHistoryStore) { + $this->markingHistoryStore->updateMarkingHistory($subject, $transition, $marking, $this->getName()); + } + $this->entered($subject, $transition, $marking); $this->completed($subject, $transition, $marking); @@ -363,4 +369,9 @@ private function announce($subject, Transition $initialTransition, Marking $mark $this->dispatcher->dispatch(sprintf('workflow.%s.announce.%s', $this->name, $transition->getName()), $event); } } + + public function getMarkingHistoryStore(): ?MarkingHistoryStore + { + return $this->markingHistoryStore; + } } diff --git a/src/Symfony/Component/Workflow/WorkflowInterface.php b/src/Symfony/Component/Workflow/WorkflowInterface.php index 5a1f2c74e81aa..a38379bb3f4be 100644 --- a/src/Symfony/Component/Workflow/WorkflowInterface.php +++ b/src/Symfony/Component/Workflow/WorkflowInterface.php @@ -85,4 +85,11 @@ public function getDefinition(); public function getMarkingStore(); public function getMetadataStore(): MetadataStoreInterface; + + /** + * Returns the optional Marking History Store for logging the state history on the entity. + * + * @return null|MarkingHistoryStore + */ + public function getMarkingHistoryStore(): ?MarkingHistoryStore; }