It has functionality to accept a Rule (guard logic) and a Command (transition logic) as well as callables for the guard logic and transition logic . callables are: closures, anonymous functions, user defined functions, instance methods, static methods etc. see the php manual. The guards are used to check whether a transition can take place (Rule and callable) The logic parts are used to execute the transition logic (Command and callable) Rules and commands should be able to be found/autoloaded by the application If transitions share the same states (both to and from) then they should point to the same object reference (same states should share the exact same state configuration).
 protected function createLoader()
 {
     //we use the array loader
     //in a non-example situation we would use a backend like a
     //database for example
     //@see PDO adapter and loader
     //define the states
     $new = new State('new', State::TYPE_INITIAL);
     $green = new State('green', State::TYPE_NORMAL, State::COMMAND_NULL);
     $orange = new State('orange', State::TYPE_NORMAL);
     $red = new State('red', State::TYPE_NORMAL);
     //create the transtions by using the states
     $ng = new Transition($new, $green, 'go-green', Transition::RULE_TRUE, Transition::COMMAND_NULL);
     $go = new Transition($green, $orange, 'go-orange', 'izzum\\examples\\trafficlight\\rules\\CanSwitch', 'izzum\\examples\\trafficlight\\command\\SwitchOrange');
     $or = new Transition($orange, $red, 'go-red', 'izzum\\examples\\trafficlight\\rules\\CanSwitch', 'izzum\\examples\\trafficlight\\command\\SwitchRed');
     $rg = new Transition($red, $green, 'go-green', 'izzum\\examples\\trafficlight\\rules\\CanSwitch', 'izzum\\examples\\trafficlight\\command\\SwitchGreen');
     //set some descriptions for uml generation
     $ng->setDescription("from green to orange. use the switch to orange command");
     $go->setDescription("from new to green. this will start the cycle");
     $or->setDescription("from orange to red. use the appropriate command");
     $rg->setDescription("from red back to green.");
     $new->setDescription('the init state');
     $green->setDescription("go!");
     $orange->setDescription("looks like a shade of green...");
     $red->setDescription('stop');
     $transitions[] = $ng;
     $transitions[] = $go;
     $transitions[] = $or;
     $transitions[] = $rg;
     $loader = new LoaderArray($transitions);
     return $loader;
 }
示例#2
0
 /**
  * add an outgoing transition from this state.
  *
  * TRICKY: this method should be package visibility only,
  * so don't use directly. it is used to set the bidirectional association
  * for State and Transition from a Transition instance on the state the transition will be allowed to 
  * run from ('state from').
  *
  * @param Transition $transition            
  * @return boolan yes in case the transition was not on the State already or in case of an invalid transition
  */
 public function addTransition(Transition $transition)
 {
     $output = false;
     // check all existing transitions.
     if (!$this->hasTransition($transition->getName()) && $transition->getStateFrom()->getName() == $this->getName() && !$this->isFinal() && !$this->isRegex()) {
         $output = true;
         $this->transitions[] = $transition;
     }
     return $output;
 }
 /**
  * A template method that Stores a failed transition in the storage facility for
  * historical/analytical purposes.
  *
  * @param Identifier $identifier            
  * @param Transition $transition            
  * @param \Exception $e            
  */
 public function setFailedTransition(Identifier $identifier, Transition $transition, \Exception $e)
 {
     // check if it is persisted, otherwise we cannot get the current state
     $message = new \stdClass();
     $message->code = $e->getCode();
     $message->transition = $transition->getName();
     $message->message = $e->getMessage();
     $message->file = $e->getFile();
     $message->line = $e->getLine();
     if ($this->isPersisted($identifier)) {
         /*
         a transition can fail even after a state has been set in the transition process,
         for example when executing the code in the entry action of the new state,
         making the transition partly failed.
         the history will then show a succesful transition to the new state first,
         and here we will then add the failure of the transition with the current state (which is the 'to' state of the transition)
         and with the failure message.
         In case that the transition failed before the state has been set
         then this will be put in the history of transitions with the 'from' state as the current state.
         */
         $state = $this->getState($identifier);
     } else {
         //no current state available in persistence layer.
         //this is exceptional and should not happen when configured correctly and
         //if the machine has been 'added' or if a transition has been (partly) mande.
         //therefore, it must be the from state..
         $state = $transition->getStateFrom()->getName();
     }
     $message->state = $state;
     $this->addHistory($identifier, $state, $message, true);
 }
 /**
  * Add the transition, after it has previously been checked that is did not
  * contain states with a regex.
  *
  * @param Transition $transition   
  * @return boolean true in case it was added. false otherwise         
  */
 protected function addTransitionWithoutRegex(Transition $transition)
 {
     // don't allow transitions from a final state
     if ($transition->getStateFrom()->isFinal()) {
         return false;
     }
     // add the transition only if it already exists (no overwrites)
     if ($this->getTransition($transition->getName()) !== null) {
         return false;
     }
     $this->transitions[$transition->getName()] = $transition;
     $from = $transition->getStateFrom();
     // adds state only if it does not already exist (no overwrites)
     $this->addState($from);
     /* 
      * transitions create bidirectional references to the States
      * when they are made, but here the States that are set on the machine
      * can actually be different instances from different transitions (eg:
      * a->b and a->c => we now have two State instances of a)
      * we therefore need to merge the transitions on the existing states.
      * The LoaderArray class does this for us by default, but we do it here
      * too, just in case a client decides to call the 'addTransition' method
      * directly without a loader.
      */
     //get the state known to the machine, to prevent possible bug where client
     //creates 2 different state instances with different configs. we won't know
     //how to resolve this anyway, so just pick the existing state (first in wins)
     $state = $this->getState($from->getName());
     // adds transition only if it does not already exist (no overwrites)
     $state->addTransition($transition);
     $to = $transition->getStateTo();
     // adds state only if it dooes not already exist (no overwrites)
     $this->addState($to);
     return true;
 }
示例#5
0
 /**
  * {@inheritDoc}
  */
 public function load(StateMachine $stateMachine)
 {
     //decode the json in a php object structure
     $decoded = yaml_parse($this->getYaml(), false);
     //yaml decoding returns a php array.
     $name = $stateMachine->getContext()->getMachine();
     $found = false;
     $data = null;
     if (is_array(@$decoded['machines'])) {
         foreach ($decoded['machines'] as $data) {
             if ($data['name'] === $name) {
                 $found = true;
                 break;
             }
         }
     }
     if (!$found) {
         //no name match found
         throw new Exception(sprintf('no machine data found for "%s" in yaml. seems like a wrong configuration.', $name), Exception::BAD_LOADERDATA);
     }
     //accessing an array with an @ error suppresion operator ('shut the f**k up' operator),
     //allows you to get properties, even if they do not exist, without notices.
     //this lets us be a little lazy in mapping the array properties to the state and transition properties
     $states = array();
     foreach ($data['states'] as $state) {
         $tmp = new State($state['name'], $state['type'], @$state['entry_command'], @$state['exit_command'], @$state['entry_callable'], @$state['exit_callable']);
         $tmp->setDescription(@$state['description']);
         $states[$tmp->getName()] = $tmp;
     }
     $transitions = array();
     foreach ($data['transitions'] as $transition) {
         $tmp = new Transition($states[$transition['state_from']], $states[$transition['state_to']], @$transition['event'], @$transition['rule'], @$transition['command'], @$transition['guard_callable'], @$transition['transition_callable']);
         $tmp->setDescription(@$transition['description']);
         $transitions[] = $tmp;
     }
     //delegate to loader
     $loader = new LoaderArray($transitions);
     return $loader->load($stateMachine);
 }
 /**
  * gets all data for transitions.
  * This method is public for testing purposes
  * 
  * @param string $machine
  *            the machine name
  * @return Transition[]
  */
 public function getLoaderData($machine)
 {
     $rows = $this->getTransitions($machine);
     $output = array();
     // array for local caching of states
     $states = array();
     foreach ($rows as $row) {
         $state_from = $row['state_from'];
         $state_to = $row['state_to'];
         // create the 'from' state
         if (isset($states[$state_from])) {
             $from = $states[$state_from];
         } else {
             $from = new State($row['state_from'], $row['state_from_type'], $row['state_from_entry_command'], $row['state_from_exit_command']);
             $from->setDescription($row['state_from_description']);
         }
         // cache the 'from' state for the next iterations
         $states[$from->getName()] = $from;
         // create the 'to' state
         if (isset($states[$state_to])) {
             $to = $states[$state_to];
         } else {
             $to = new State($row['state_to'], $row['state_to_type'], $row['state_to_entry_command'], $row['state_to_exit_command']);
             $to->setDescription($row['state_to_description']);
         }
         // cache to 'to' state for the next iterations
         $states[$to->getName()] = $to;
         // build the transition
         $transition = new Transition($from, $to, $row['event'], $row['rule'], $row['command']);
         $transition->setDescription($row['transition_description']);
         $output[] = $transition;
     }
     return $output;
 }
 /**
  * {@inheritDoc}
  */
 public function load(StateMachine $stateMachine)
 {
     //load the xml in a php object structure. suppres warning with @ operator since we explicitely check the return value
     $xml = @simplexml_load_string($this->getXML());
     if ($xml === false) {
         //could not load
         throw new Exception(sprintf('could not load xml data. check the xml format'), Exception::BAD_LOADERDATA);
     }
     $name = $stateMachine->getContext()->getMachine();
     $found = false;
     $data = null;
     foreach ($xml->machine as $data) {
         if ((string) @$data->name === $name) {
             $found = true;
             break;
         }
     }
     if (!$found) {
         //no name match found
         throw new Exception(sprintf('no machine data found for %s in xml. seems like a wrong configuration.', $name), Exception::BAD_LOADERDATA);
     }
     //accessing xml as an object with the @ error suppresion operator ('shut the f**k up' operator)
     //allows you to get properties, even if they do not exist, without notices.
     //this let's us be a littlebit lazy since we know some nonessential properties could not be there
     $states = array();
     foreach ($data->states->state as $state) {
         $tmp = new State((string) $state->name, (string) $state->type, (string) @$state->entry_command, (string) @$state->exit_command, (string) @$state->entry_callable, (string) @$state->exit_callable);
         $tmp->setDescription((string) @$state->description);
         $states[$tmp->getName()] = $tmp;
     }
     $transitions = array();
     foreach ($data->transitions->transition as $transition) {
         $tmp = new Transition($states[(string) @$transition->state_from], $states[(string) @$transition->state_to], (string) @$transition->event, (string) @$transition->rule, (string) @$transition->command, (string) @$transition->guard_callable, (string) @$transition->transition_callable);
         $tmp->setDescription((string) @$transition->description);
         $transitions[] = $tmp;
     }
     //delegate to loader
     $loader = new LoaderArray($transitions);
     return $loader->load($stateMachine);
 }
示例#8
0
 /**
  * {@inheritDoc}
  */
 public function load(StateMachine $stateMachine)
 {
     //decode the json in a php object structure
     $decoded = json_decode($this->getJSON(), false);
     if (!$decoded) {
         //could not decode (make sure that fully qualified names are escaped with
         //2 backslashes: \\izzum\\commands\\Null and that only double quotes are used.
         throw new Exception(sprintf('could not decode json data. did you only use double quotes? check the json format against %s', 'http://jsonlint.com/'), Exception::BAD_LOADERDATA);
     }
     $name = $stateMachine->getContext()->getMachine();
     $found = false;
     if (is_array(@$decoded->machines)) {
         foreach ($decoded->machines as $data) {
             if ($data->name === $name) {
                 $found = true;
                 break;
             }
         }
     }
     if (!$found) {
         //no name match found
         throw new Exception(sprintf('no machine data found for %s in json. seems like a wrong configuration.', $name), Exception::BAD_LOADERDATA);
     }
     //accessing json as an object with an @ error suppresion operator ('shut the f**k up' operator),
     //allows you to get properties, even if they do not exist, without notices.
     //this lets us be a little lazy in mapping the json properties to the state and transition properties
     $states = array();
     foreach ($data->states as $state) {
         $tmp = new State($state->name, $state->type, @$state->entry_command, @$state->exit_command, @$state->entry_callable, @$state->exit_callable);
         $tmp->setDescription(@$state->description);
         $states[$tmp->getName()] = $tmp;
     }
     $transitions = array();
     foreach ($data->transitions as $transition) {
         $tmp = new Transition($states[$transition->state_from], $states[$transition->state_to], @$transition->event, @$transition->rule, @$transition->command, @$transition->guard_callable, @$transition->transition_callable);
         $tmp->setDescription(@$transition->description);
         $transitions[] = $tmp;
     }
     //delegate to loader
     $loader = new LoaderArray($transitions);
     return $loader->load($stateMachine);
 }
 /**
  * add/overwrite a transition
  *
  * @param Transition $transition            
  */
 public function add(Transition $transition)
 {
     $this->transitions[$transition->getName()] = $transition;
 }
 /**
  * @test
  */
 public function shouldThrowExceptionFromAppliedCommand()
 {
     $from = new State('a');
     $to = new State('b');
     $rule = 'izzum\\rules\\TrueRule';
     $command = 'izzum\\command\\ExceptionCommand';
     $object = new Context(new Identifier(Identifier::NULL_ENTITY_ID, Identifier::NULL_STATEMACHINE));
     $transition = new Transition($from, $to, null, $rule, $command);
     try {
         $transition->process($object);
         $this->fail('should not come here');
     } catch (Exception $e) {
         $this->assertEquals(Exception::COMMAND_EXECUTION_FAILURE, $e->getCode());
     }
     $this->assertTrue($transition->can($object));
 }
 protected function _onEnterState(Transition $transition)
 {
     //echo '_onEnterState: ' . $transition . PHP_EOL;
     $state = $transition->getStateTo()->getName();
     switch ($state) {
         case "posing":
         case "fighting":
         case "resqueing":
         case "superhero":
         case "normal":
             echo "{$state} ";
             $this->updateStatistics($state);
             break;
         default:
             break;
     }
 }