diff --git a/.travis.yml b/.travis.yml index 3c7908c..c66b8b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,7 @@ language: php sudo: false +# disabled old php build (http://php.net/eol.php) php: - - 5.3 - - 5.4 - - 5.5 - 5.6 - 7 - hhvm diff --git a/src/Finite/Bundle/FiniteBundle/DependencyInjection/Compiler/ContainerCallbackPass.php b/src/Finite/Bundle/FiniteBundle/DependencyInjection/Compiler/ContainerCallbackPass.php index 9addf27..aa457d4 100644 --- a/src/Finite/Bundle/FiniteBundle/DependencyInjection/Compiler/ContainerCallbackPass.php +++ b/src/Finite/Bundle/FiniteBundle/DependencyInjection/Compiler/ContainerCallbackPass.php @@ -2,6 +2,7 @@ namespace Finite\Bundle\FiniteBundle\DependencyInjection\Compiler; +use Finite\Event\Callback\Callback; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; @@ -24,14 +25,14 @@ public function process(ContainerBuilder $container) $definition = $container->getDefinition($id); $config = $definition->getArgument(0); if (isset($config['callbacks'])) { - foreach (array('before', 'after') as $position) { + foreach (array(Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER) as $position) { foreach ($config['callbacks'][$position] as &$callback) { if ( - is_array($callback['do']) - && 0 === strpos($callback['do'][0], '@') - && $container->hasDefinition(substr($callback['do'][0], 1)) + is_array($callback[Callback::CLAUSE_DO]) + && 0 === strpos($callback[Callback::CLAUSE_DO][0], '@') + && $container->hasDefinition(substr($callback[Callback::CLAUSE_DO][0], 1)) ) { - $callback['do'][0] = new Reference(substr($callback['do'][0], 1)); + $callback[Callback::CLAUSE_DO][0] = new Reference(substr($callback[Callback::CLAUSE_DO][0], 1)); } } } diff --git a/src/Finite/Bundle/FiniteBundle/DependencyInjection/Configuration.php b/src/Finite/Bundle/FiniteBundle/DependencyInjection/Configuration.php index 5a2b8d0..72e1962 100644 --- a/src/Finite/Bundle/FiniteBundle/DependencyInjection/Configuration.php +++ b/src/Finite/Bundle/FiniteBundle/DependencyInjection/Configuration.php @@ -2,6 +2,7 @@ namespace Finite\Bundle\FiniteBundle\DependencyInjection; +use Finite\Event\Callback\Callback; use Symfony\Component\Config\Definition\Builder\NodeBuilder; use Symfony\Component\Config\Definition\Builder\TreeBuilder; use Symfony\Component\Config\Definition\ConfigurationInterface; @@ -86,8 +87,8 @@ protected function addTransitionSection(NodeBuilder $rootProto) protected function addCallbackSection(NodeBuilder $rootProto) { $callbacks = $rootProto->arrayNode('callbacks')->children(); - $this->addSubCallbackSection($callbacks, 'before'); - $this->addSubCallbackSection($callbacks, 'after'); + $this->addSubCallbackSection($callbacks, Callback::CLAUSE_BEFORE); + $this->addSubCallbackSection($callbacks, Callback::CLAUSE_AFTER); $callbacks->end()->end(); } diff --git a/src/Finite/Bundle/FiniteBundle/DependencyInjection/FiniteFiniteExtension.php b/src/Finite/Bundle/FiniteBundle/DependencyInjection/FiniteFiniteExtension.php index d28bfd0..a6d3626 100644 --- a/src/Finite/Bundle/FiniteBundle/DependencyInjection/FiniteFiniteExtension.php +++ b/src/Finite/Bundle/FiniteBundle/DependencyInjection/FiniteFiniteExtension.php @@ -2,6 +2,7 @@ namespace Finite\Bundle\FiniteBundle\DependencyInjection; +use Finite\Event\Callback\Callback; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader; @@ -68,7 +69,7 @@ protected function removeDisabledCallbacks(array $config) return $config; } - foreach (array('before', 'after') as $position) { + foreach (array(Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER) as $position) { foreach ($config['callbacks'][$position] as $i => $callback) { if ($callback['disabled']) { unset($config['callbacks'][$position][$i]); diff --git a/src/Finite/Bundle/FiniteBundle/Resources/config/services.xml b/src/Finite/Bundle/FiniteBundle/Resources/config/services.xml index 2045953..484ca1d 100644 --- a/src/Finite/Bundle/FiniteBundle/Resources/config/services.xml +++ b/src/Finite/Bundle/FiniteBundle/Resources/config/services.xml @@ -42,5 +42,4 @@ - diff --git a/src/Finite/Event/Callback/Callback.php b/src/Finite/Event/Callback/Callback.php index 414aabe..3110341 100644 --- a/src/Finite/Event/Callback/Callback.php +++ b/src/Finite/Event/Callback/Callback.php @@ -9,19 +9,26 @@ */ class Callback implements CallbackInterface { + const CLAUSE_AFTER = 'after'; + const CLAUSE_BEFORE = 'before'; + const CLAUSE_FROM = 'from'; + const CLAUSE_TO = 'to'; + const CLAUSE_ON = 'on'; + const CLAUSE_DO = 'do'; + /** * @var CallbackSpecificationInterface */ private $specification; /** - * @var callable + * @var array callable */ private $callable; /** * @param CallbackSpecificationInterface $callbackSpecification - * @param callable $callable + * @param $callable */ public function __construct(CallbackSpecificationInterface $callbackSpecification, $callable) { @@ -37,6 +44,22 @@ public function getSpecification() return $this->specification; } + /** + * @return array callable + */ + public function getCallbacks() + { + return $this->callable; + } + + /** + * @return array + */ + public function getClauses() + { + return $this->specification->getClauses(); + } + /** * {@inheritdoc} */ diff --git a/src/Finite/Event/Callback/CallbackInterface.php b/src/Finite/Event/Callback/CallbackInterface.php index 31a612e..d3a621d 100644 --- a/src/Finite/Event/Callback/CallbackInterface.php +++ b/src/Finite/Event/Callback/CallbackInterface.php @@ -15,4 +15,19 @@ interface CallbackInterface * @param TransitionEvent $event */ public function __invoke(TransitionEvent $event); + + /** + * @return CallbackSpecificationInterface + */ + public function getSpecification(); + + /** + * @return array callable + */ + public function getCallbacks(); + + /** + * @return array + */ + public function getClauses(); } diff --git a/src/Finite/Event/Callback/CallbackSpecification.php b/src/Finite/Event/Callback/CallbackSpecification.php index df4de77..476b542 100644 --- a/src/Finite/Event/Callback/CallbackSpecification.php +++ b/src/Finite/Event/Callback/CallbackSpecification.php @@ -36,7 +36,7 @@ public function __construct(StateMachineInterface $sm, array $from, array $to, a $isExclusion = function ($str) { return 0 === strpos($str, '-'); }; $removeDash = function ($str) { return substr($str, 1); }; - foreach (array('from', 'to', 'on') as $clause) { + foreach (array(Callback::CLAUSE_FROM, Callback::CLAUSE_TO, Callback::CLAUSE_ON) as $clause) { $excludedClause = 'excluded_'.$clause; $this->specs[$excludedClause] = array_filter(${$clause}, $isExclusion); @@ -58,9 +58,17 @@ public function isSatisfiedBy(TransitionEvent $event) { return $event->getStateMachine() === $this->stateMachine && - $this->supportsClause('from', $event->getInitialState()->getName()) && - $this->supportsClause('to', $event->getTransition()->getState()) && - $this->supportsClause('on', $event->getTransition()->getName()); + $this->supportsClause(Callback::CLAUSE_FROM, $event->getInitialState()->getName()) && + $this->supportsClause(Callback::CLAUSE_TO, $event->getTransition()->getState()) && + $this->supportsClause(Callback::CLAUSE_ON, $event->getTransition()->getName()); + } + + /** + * @return array + */ + public function getClauses() + { + return $this->specs; } /** diff --git a/src/Finite/Event/Callback/CallbackSpecificationInterface.php b/src/Finite/Event/Callback/CallbackSpecificationInterface.php index 7ad19dc..6297a21 100644 --- a/src/Finite/Event/Callback/CallbackSpecificationInterface.php +++ b/src/Finite/Event/Callback/CallbackSpecificationInterface.php @@ -19,4 +19,9 @@ interface CallbackSpecificationInterface * @return bool */ public function isSatisfiedBy(TransitionEvent $event); + + /** + * @return array + */ + public function getClauses(); } diff --git a/src/Finite/Event/CallbackHandler.php b/src/Finite/Event/CallbackHandler.php index d81595c..ec1e7ec 100644 --- a/src/Finite/Event/CallbackHandler.php +++ b/src/Finite/Event/CallbackHandler.php @@ -40,23 +40,23 @@ public function __construct(EventDispatcherInterface $dispatcher) $this->specResolver = new OptionsResolver(); $this->specResolver->setDefaults( array( - 'on' => self::ALL, - 'from' => self::ALL, - 'to' => self::ALL, + Callback::CLAUSE_ON => self::ALL, + Callback::CLAUSE_FROM => self::ALL, + Callback::CLAUSE_TO => self::ALL, ) ); - $this->specResolver->setAllowedTypes('on', array('string', 'array')); - $this->specResolver->setAllowedTypes('from', array('string', 'array')); - $this->specResolver->setAllowedTypes('to', array('string', 'array')); + $this->specResolver->setAllowedTypes(Callback::CLAUSE_ON, array('string', 'array')); + $this->specResolver->setAllowedTypes(Callback::CLAUSE_FROM, array('string', 'array')); + $this->specResolver->setAllowedTypes(Callback::CLAUSE_TO, array('string', 'array')); $toArrayNormalizer = function (Options $options, $value) { return (array) $value; }; - $this->specResolver->setNormalizer('on', $toArrayNormalizer); - $this->specResolver->setNormalizer('from', $toArrayNormalizer); - $this->specResolver->setNormalizer('to', $toArrayNormalizer); + $this->specResolver->setNormalizer(Callback::CLAUSE_ON, $toArrayNormalizer); + $this->specResolver->setNormalizer(Callback::CLAUSE_FROM, $toArrayNormalizer); + $this->specResolver->setNormalizer(Callback::CLAUSE_TO, $toArrayNormalizer); } /** @@ -109,7 +109,7 @@ protected function add($smOrCallback, $event, $callable = null, array $specs = a ); $specs = $this->specResolver->resolve($specs); - $callback = CallbackBuilder::create($smOrCallback, $specs['from'], $specs['to'], $specs['on'], $callable)->getCallback(); + $callback = CallbackBuilder::create($smOrCallback, $specs[Callback::CLAUSE_FROM], $specs[Callback::CLAUSE_TO], $specs[Callback::CLAUSE_ON], $callable)->getCallback(); $this->dispatcher->addListener($event, $callback); diff --git a/src/Finite/Loader/ArrayLoader.php b/src/Finite/Loader/ArrayLoader.php index d714984..0cf8722 100644 --- a/src/Finite/Loader/ArrayLoader.php +++ b/src/Finite/Loader/ArrayLoader.php @@ -2,6 +2,7 @@ namespace Finite\Loader; +use Finite\Event\Callback\Callback; use Finite\Event\Callback\CallbackBuilderFactory; use Finite\Event\Callback\CallbackBuilderFactoryInterface; use Finite\Event\CallbackHandler; @@ -72,9 +73,9 @@ public function load(StateMachineInterface $stateMachine) $stateMachine->setStateAccessor(new PropertyPathStateAccessor($this->config['property_path'])); $stateMachine->setGraph($this->config['graph']); + $this->loadCallbacks($stateMachine); $this->loadStates($stateMachine); $this->loadTransitions($stateMachine); - $this->loadCallbacks($stateMachine); } /** @@ -87,6 +88,55 @@ public function supports($object, $graph = 'default') return $reflection->isInstance($object) && $graph === $this->config['graph']; } + /** + * @param $triggerName + * + * @return array + */ + protected function findCallbacksByTrigger($triggerName) + { + $callbacks = []; + + foreach ([Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER] as $position) { + $callbacks[$position] = []; + + $callbacks[$position] = array_merge( + $callbacks[$position], + $this->findCallbacksByTriggerAndPosition($triggerName, $position) + ); + } + + return $callbacks; + } + + /** + * @param $triggerName + * @param $position + * + * @return array + */ + protected function findCallbacksByTriggerAndPosition($triggerName, $position) + { + $callbacks = []; + + if (empty($this->config['callbacks'][$position])) { + return $callbacks; + } + + foreach ($this->config['callbacks'][$position] as $callbackName => $callback) { + foreach ([Callback::CLAUSE_FROM, Callback::CLAUSE_TO, Callback::CLAUSE_ON] as $clause) { + if (!empty($callback[$clause])) { + if ((is_string($callback[$clause]) && $callback[$clause] === $triggerName) + || (is_array($callback[$clause]) && in_array($triggerName, $callback[$clause]))) { + $callbacks[] = [$callbackName => $callback]; + } + } + } + } + + return $callbacks; + } + /** * @param StateMachineInterface $stateMachine */ @@ -102,7 +152,13 @@ private function loadStates(StateMachineInterface $stateMachine) foreach ($this->config['states'] as $state => $config) { $config = $resolver->resolve($config); - $stateMachine->addState(new State($state, $config['type'], array(), $config['properties'])); + $stateMachine->addState(new State( + $state, + $config['type'], + [], + $config['properties'], + $this->findCallbacksByTrigger($state) + )); } } @@ -112,12 +168,12 @@ private function loadStates(StateMachineInterface $stateMachine) private function loadTransitions(StateMachineInterface $stateMachine) { $resolver = new OptionsResolver(); - $resolver->setRequired(array('from', 'to')); + $resolver->setRequired(array(Callback::CLAUSE_FROM, Callback::CLAUSE_TO)); $resolver->setDefaults(array('guard' => null, 'configure_properties' => null, 'properties' => array())); $resolver->setAllowedTypes('configure_properties', array('null', 'callable')); - $resolver->setNormalizer('from', function (Options $options, $v) { return (array) $v; }); + $resolver->setNormalizer(Callback::CLAUSE_FROM, function (Options $options, $v) { return (array) $v; }); $resolver->setNormalizer('guard', function (Options $options, $v) { return !isset($v) ? null : $v; }); $resolver->setNormalizer('configure_properties', function (Options $options, $v) { $resolver = new OptionsResolver(); @@ -136,10 +192,11 @@ private function loadTransitions(StateMachineInterface $stateMachine) $stateMachine->addTransition( new Transition( $transition, - $config['from'], + $config[Callback::CLAUSE_FROM], $config['to'], $config['guard'], - $config['configure_properties'] + $config['configure_properties'], + $this->findCallbacksByTrigger($transition) ) ); } @@ -154,7 +211,9 @@ private function loadCallbacks(StateMachineInterface $stateMachine) return; } - foreach (array('before', 'after') as $position) { + $stateMachine->setCallbacks($this->config['callbacks']); + + foreach (array(Callback::CLAUSE_BEFORE, Callback::CLAUSE_AFTER) as $position) { $this->loadCallbacksFor($position, $stateMachine); } } @@ -171,10 +230,10 @@ private function loadCallbacksFor($position, $stateMachine) $specs = $resolver->resolve($specs); $callback = $this->callbackBuilderFactory->createBuilder($stateMachine) - ->setFrom($specs['from']) - ->setTo($specs['to']) - ->setOn($specs['on']) - ->setCallable($specs['do']) + ->setFrom($specs[Callback::CLAUSE_FROM]) + ->setTo($specs[Callback::CLAUSE_TO]) + ->setOn($specs[Callback::CLAUSE_ON]) + ->setCallable($specs[Callback::CLAUSE_DO]) ->getCallback(); $this->callbackHandler->$method($callback); @@ -187,24 +246,24 @@ private function getCallbacksResolver() $resolver->setDefaults( array( - 'on' => array(), - 'from' => array(), - 'to' => array(), + Callback::CLAUSE_ON => array(), + Callback::CLAUSE_FROM => array(), + Callback::CLAUSE_TO => array(), ) ); - $resolver->setRequired(array('do')); + $resolver->setRequired(array(Callback::CLAUSE_DO)); - $resolver->setAllowedTypes('on', array('string', 'array')); - $resolver->setAllowedTypes('from', array('string', 'array')); - $resolver->setAllowedTypes('to', array('string', 'array')); + $resolver->setAllowedTypes(Callback::CLAUSE_ON, array('string', 'array')); + $resolver->setAllowedTypes(Callback::CLAUSE_FROM, array('string', 'array')); + $resolver->setAllowedTypes(Callback::CLAUSE_TO, array('string', 'array')); $toArrayNormalizer = function (Options $options, $value) { return (array) $value; }; - $resolver->setNormalizer('on', $toArrayNormalizer); - $resolver->setNormalizer('from', $toArrayNormalizer); - $resolver->setNormalizer('to', $toArrayNormalizer); + $resolver->setNormalizer(Callback::CLAUSE_ON, $toArrayNormalizer); + $resolver->setNormalizer(Callback::CLAUSE_FROM, $toArrayNormalizer); + $resolver->setNormalizer(Callback::CLAUSE_TO, $toArrayNormalizer); return $resolver; } diff --git a/src/Finite/State/State.php b/src/Finite/State/State.php index bc4c06b..4678df5 100644 --- a/src/Finite/State/State.php +++ b/src/Finite/State/State.php @@ -27,6 +27,13 @@ class State implements StateInterface */ protected $transitions; + /** + * Callbacks of the state + * + * @var array + */ + protected $callbacks; + /** * The state name. * @@ -39,12 +46,27 @@ class State implements StateInterface */ protected $properties; - public function __construct($name, $type = self::TYPE_NORMAL, array $transitions = array(), array $properties = array()) - { + /** + * State constructor. + * + * @param $name + * @param string $type + * @param array $transitions + * @param array $properties + * @param array $callbacks + */ + public function __construct( + $name, + $type = self::TYPE_NORMAL, + array $transitions = [], + array $properties = [], + array $callbacks = [] + ) { $this->name = $name; $this->type = $type; $this->transitions = $transitions; $this->properties = $properties; + $this->callbacks = $callbacks; } /** @@ -163,6 +185,14 @@ public function setProperties(array $properties) $this->properties = $properties; } + /** + * @return array + */ + public function getCallbacks() + { + return $this->callbacks; + } + /** * @return string */ diff --git a/src/Finite/State/StateInterface.php b/src/Finite/State/StateInterface.php index e497d29..fec4279 100644 --- a/src/Finite/State/StateInterface.php +++ b/src/Finite/State/StateInterface.php @@ -69,4 +69,9 @@ public function getTransitions(); * @deprecated Deprecated since version 1.0.0-BETA2. Use {@link StateMachine::can($transition)} instead. */ public function can($transition); + + /** + * @return array + */ + public function getCallbacks(); } diff --git a/src/Finite/StateMachine/StateMachine.php b/src/Finite/StateMachine/StateMachine.php index b928cee..2e8db57 100644 --- a/src/Finite/StateMachine/StateMachine.php +++ b/src/Finite/StateMachine/StateMachine.php @@ -2,6 +2,7 @@ namespace Finite\StateMachine; +use Finite\Event\Callback\Callback; use Finite\Event\FiniteEvents; use Finite\Event\StateMachineEvent; use Finite\Event\TransitionEvent; @@ -65,6 +66,11 @@ class StateMachine implements StateMachineInterface */ protected $graph; + /** + * @var array + */ + protected $callbacks = []; + /** * @param object $object * @param EventDispatcherInterface $dispatcher @@ -93,7 +99,7 @@ public function initialize() $initialState = $this->stateAccessor->getState($this->object); } catch (Exception\NoSuchPropertyException $e) { throw new Exception\ObjectException(sprintf( - 'StateMachine can\'t be initialized because the defined property_path of object "%s" does not exist.', + 'StateMachine can\'t be initialized because the defined property_path of object "%s" does not exist.', get_class($this->object) ), $e->getCode(), $e); } @@ -395,4 +401,20 @@ private function dispatchTransitionEvent(TransitionInterface $transition, Transi $this->dispatcher->dispatch($transitionState.'.'.$this->getGraph().'.'.$transition->getName(), $event); } } + + /** + * @return array + */ + public function getCallbacks() + { + return $this->callbacks; + } + + /** + * @return array + */ + public function setCallbacks($callbacks) + { + $this->callbacks = $callbacks; + } } diff --git a/src/Finite/StateMachine/StateMachineInterface.php b/src/Finite/StateMachine/StateMachineInterface.php index 6f82a6e..63d7046 100644 --- a/src/Finite/StateMachine/StateMachineInterface.php +++ b/src/Finite/StateMachine/StateMachineInterface.php @@ -131,4 +131,14 @@ public function getGraph(); * @return bool */ public function findStateWithProperty($property, $value = null); + + /** + * @return array + */ + public function setCallbacks($callbacks); + + /** + * @return array + */ + public function getCallbacks(); } diff --git a/src/Finite/Transition/Transition.php b/src/Finite/Transition/Transition.php index 9e5e047..f18bae2 100644 --- a/src/Finite/Transition/Transition.php +++ b/src/Finite/Transition/Transition.php @@ -43,19 +43,26 @@ class Transition implements PropertiesAwareTransitionInterface */ protected $propertiesOptionsResolver; + /** + * @var array + */ + protected $callbacks; + /** * @param string $name * @param string|array $initialStates * @param string $state * @param callable|null $guard * @param OptionsResolver $propertiesOptionsResolver + * @param array $callbacks */ public function __construct( $name, $initialStates, $state, $guard = null, - OptionsResolver $propertiesOptionsResolver = null + OptionsResolver $propertiesOptionsResolver = null, + array $callbacks = [] ) { if (null !== $guard && !is_callable($guard)) { throw new \InvalidArgumentException('Invalid callable guard argument passed to Transition::__construct().'); @@ -66,6 +73,7 @@ public function __construct( $this->initialStates = (array) $initialStates; $this->guard = $guard; $this->propertiesOptionsResolver = $propertiesOptionsResolver ?: new OptionsResolver(); + $this->callbacks = $callbacks; } /** @@ -178,6 +186,14 @@ public function getProperties() ); } + /** + * @return array + */ + public function getCallbacks() + { + return $this->callbacks; + } + /** * @return string */ diff --git a/src/Finite/Transition/TransitionInterface.php b/src/Finite/Transition/TransitionInterface.php index f13676c..57f38d7 100644 --- a/src/Finite/Transition/TransitionInterface.php +++ b/src/Finite/Transition/TransitionInterface.php @@ -47,4 +47,10 @@ public function getName(); * @return callable */ public function getGuard(); + + /** + * Returns properties in the transition. + * return mixed + */ + public function getProperties(); }