|  | <?php | 
        
          |  | use Finite\StateMachine\StateMachine, Finite\State\State, Finite\Event\FiniteEvents, | 
        
          |  | Finite\Event\TransitionEvent, Finite\StateMachine\ListenableStateMachine; | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * The FiniteStateMachine Trait. | 
        
          |  | * It provides easy ways to deal with Stateful objects and StateMachine | 
        
          |  | * Prerequisite: install Finite package (https://github.com/yohang/Finite#readme) | 
        
          |  | * Usage: in your Stateful Class, add the stateMachineRules() method | 
        
          |  | *  and call initStateMachine() method at initialization (__contruct() method) | 
        
          |  | * | 
        
          |  | * @author Tortue Torche <[email protected]> | 
        
          |  | */ | 
        
          |  | trait FiniteStateMachine | 
        
          |  | { | 
        
          |  | /** | 
        
          |  | * @var Finite\StateMachine\StateMachine | 
        
          |  | */ | 
        
          |  | protected $stateMachine; | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @var array | 
        
          |  | */ | 
        
          |  | protected $finiteConfig; | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @param Finite\StateMachine\StateMachine $sm | 
        
          |  | */ | 
        
          |  | public function initFiniteEventHandlers(Finite\StateMachine\StateMachine $sm) | 
        
          |  | { | 
        
          |  | $dispatcher = new Symfony\Component\EventDispatcher\EventDispatcher; | 
        
          |  | $sm->setEventDispatcher($dispatcher); | 
        
          |  | $self = $this; | 
        
          |  |  | 
        
          |  | $dispatcher->addListener(FiniteEvents::PRE_TRANSITION, function(TransitionEvent $e) use($self) { | 
        
          |  | $this->fireTransitionCallbacks( 'before', $e->getTransition(), [ $self, $e->getTransition() ] ); | 
        
          |  | }); | 
        
          |  |  | 
        
          |  | $dispatcher->addListener(FiniteEvents::POST_TRANSITION, function(TransitionEvent $e) use($self) { | 
        
          |  | $this->fireTransitionCallbacks( 'after', $e->getTransition(), [ $self, $e->getTransition() ] ); | 
        
          |  | }); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return array | 
        
          |  | */ | 
        
          |  | protected abstract function stateMachineRules(); | 
        
          |  |  | 
        
          |  | public function initStateMachine() { | 
        
          |  | $this->finiteConfig = $this->stateMachineRules(); | 
        
          |  | $loader = new Finite\Loader\ArrayLoader($this->finiteConfig); | 
        
          |  | $sm = new ListenableStateMachine($this); | 
        
          |  |  | 
        
          |  | $this->initFiniteEventHandlers($sm); | 
        
          |  |  | 
        
          |  | $loader->load($sm); | 
        
          |  |  | 
        
          |  | $sm->initialize(); | 
        
          |  | $this->stateMachine = $sm; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * Reserved words: 'on', 'all', 'any', 'do', '-'(dash character, just after 'all' and 'any' keywords) and ','(comma character) | 
        
          |  | * | 
        
          |  | * @param string $type | 
        
          |  | * @param Finite\Transition\TransitionInterface $transition | 
        
          |  | * @param array $payload | 
        
          |  | */ | 
        
          |  | protected function fireTransitionCallbacks($type, Finite\Transition\TransitionInterface $transition, $payload = []) | 
        
          |  | { | 
        
          |  | // Previous States | 
        
          |  | $from = $transition->getInitialStates(); | 
        
          |  | // Next/Current State | 
        
          |  | $to = $transition->getState(); | 
        
          |  | // Current Transition(Event) | 
        
          |  | $event = $transition->getName(); | 
        
          |  |  | 
        
          |  | if( array_key_exists($type,  $this->finiteConfig['callbacks']) ){ | 
        
          |  | $callbacks = $this->finiteConfig['callbacks'][$type]; | 
        
          |  | $allStates = $this->getStateMachine()->getStates(); | 
        
          |  |  | 
        
          |  | foreach ($callbacks as $callback) { | 
        
          |  | $result = null; | 
        
          |  | if( array_key_exists('on', $callback) && $callback['on'] === $event ) { | 
        
          |  | $result = $callback['do']; | 
        
          |  | } else { | 
        
          |  | foreach ($callback as $key => $value) { | 
        
          |  | // TODO: DRY | 
        
          |  | if( preg_match('/^(all|any)(\s?\-\s?(.+))?$/', $key, $matchesKeys) ) { | 
        
          |  | $key = null; | 
        
          |  | if( array_key_exists(3, $matchesKeys) ) { | 
        
          |  | $key = $matchesKeys[3]; | 
        
          |  | } | 
        
          |  | $key = implode(",", array_diff( $allStates, explode(",", $key) ) ); | 
        
          |  | } | 
        
          |  | if( is_string($value) ) { | 
        
          |  | if( preg_match('/^(all|any)(\s?\-\s?(.+))?$/', $value, $matchesValues) ) { | 
        
          |  | $value = null; | 
        
          |  | if( array_key_exists(3, $matchesValues) ) { | 
        
          |  | $value = $matchesValues[3]; | 
        
          |  | } | 
        
          |  | $value = implode(",", array_diff( $allStates, explode(",", $value) ) ); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | if ( $key !== 'do' && count( array_intersect( explode(",", $key), (array) $from ) ) > 0 && in_array( $to, explode(",", $value) ) ) { | 
        
          |  | $result = $callback['do']; | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  | if($result) { | 
        
          |  | if( is_callable($callback['do']) ) { // Global or anonymous function | 
        
          |  | call_user_func_array($callback['do'], $payload); | 
        
          |  | } else { // Instance method | 
        
          |  | // Send only the transition object to method's parameters | 
        
          |  | call_user_func_array([$this, $callback['do']], [$payload[1]]); | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | } | 
        
          |  | } | 
        
          |  | } | 
        
          |  |  | 
        
          |  | public function setFiniteState($state) | 
        
          |  | { | 
        
          |  | $this->state = $state; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | public function getFiniteState() | 
        
          |  | { | 
        
          |  | return $this->state; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return Finite\StateMachine\StateMachine | 
        
          |  | */ | 
        
          |  | protected function getStateMachine() | 
        
          |  | { | 
        
          |  | return $this->stateMachine; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return Finite\State\State | 
        
          |  | */ | 
        
          |  | public function getCurrentState() | 
        
          |  | { | 
        
          |  | return $this->getStateMachine()->getCurrentState(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return string | 
        
          |  | */ | 
        
          |  | public function getState() | 
        
          |  | { | 
        
          |  | return $this->getCurrentState()->getName(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return array<string> | 
        
          |  | */ | 
        
          |  | public function getTransitions() | 
        
          |  | { | 
        
          |  | return $this->getCurrentState()->getTransitions(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @return array<string> | 
        
          |  | */ | 
        
          |  | public function getProperties() | 
        
          |  | { | 
        
          |  | return $this->getCurrentState()->getProperties(); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @param string $property | 
        
          |  | * | 
        
          |  | * @return bool | 
        
          |  | */ | 
        
          |  | public function hasProperty($property) | 
        
          |  | { | 
        
          |  | return $this->getCurrentState()->has($property); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | // Helpers | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @param string $targetState | 
        
          |  | * | 
        
          |  | * @return bool | 
        
          |  | */ | 
        
          |  | public function is($targetState) | 
        
          |  | { | 
        
          |  | return $this->getState() === $targetState; | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @param string $transitionName | 
        
          |  | * | 
        
          |  | * @return bool | 
        
          |  | */ | 
        
          |  | public function can($transitionName) | 
        
          |  | { | 
        
          |  | return $this->getStateMachine()->can($transitionName); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | /** | 
        
          |  | * @param string $transitionName | 
        
          |  | * | 
        
          |  | * @return mixed | 
        
          |  | * | 
        
          |  | * @throws Finite\Exception\StateException | 
        
          |  | */ | 
        
          |  | public function apply($transitionName) | 
        
          |  | { | 
        
          |  | return $this->getStateMachine()->apply($transitionName); | 
        
          |  | } | 
        
          |  |  | 
        
          |  | } | 
  
Hi,
I like the fact to provide shortcuts for the StateMachine use. Moreover traits allow in this case to take, or not, the feature.
But there is some things that i don't like :
I'd be proud to receive a WIP PR for working on these features.
To simplify your callback handling, i can work a bit on the actual event system (for example, firing a distinct event for each transition).