A State instance can (and should) be shared by multiple Transition objects when it is the same State for their origin/from State. A State can be a regex state (or negated regex). A regex state can be used in a transition and when added to a statemachine the regular expression will be matched on all currently known states on that statemachine and new Transitions will be added to the statemachine that match the from/to state regexes. This is very useful to build a lot of transitions very quickly. to build a full mesh of transitions (all states to all states): $a = new State('a'); $b = new State('b'); $c = new State('c'); $machine->addState($a); $machine->addState($b); $machine->addState($c); $state_regex_all = new State('regex:|.*|'); $machine->addTransition(new Transition($state_regex_all, $state_regex_all));
 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;
 }
 /**
  * sets the state as the current state and on the backend.
  * This should only be done:
  * - initially, right after a machine has been created, to set it in a
  * certain state if the state has not been persisted before.
  * - when changing context (since this resets the current state) via
  * $machine->setState($machine->getCurrentState())
  *
  * This method allows you to bypass the transition guards and the transition
  * logic. no exit/entry/transition logic will be performed
  *
  * @param State $state 
  * @param string $message optional message. this can be used by the persistence adapter
  *          to be part of the transition history to provide extra information about the transition.  
  * @throws Exception in case the state is not valid/known for this machine          
  */
 public function setState(State $state, $message = null)
 {
     if ($this->getState($state->getName()) === null) {
         throw new Exception(sprintf("%s state '%s' not known to this machine", $this->toString(), $state->getName()), Exception::SM_UNKNOWN_STATE);
     }
     //get the state known to this machine so we are sure we have the correct reference
     //even if the client provides another instance of State with the same name
     $state = $this->getState($state->getName());
     $this->getContext()->setState($state->getName(), $message);
     $this->state = $state;
 }
 /**
  * does an input regex state match a target states' name?
  *
  * @param State $regex
  *            the regex state
  * @param State $target
  *            the state to match the regular expression to
  * @return boolean
  * @link https://php.net/manual/en/function.preg-match.php
  * @link http://regexr.com/ for trying out regular expressions
  */
 public static function matchesRegex(State $regex, State $target)
 {
     $matches = false;
     if ($regex->isNormalRegex()) {
         $expression = str_replace(State::REGEX_PREFIX, '', $regex->getName());
         $matches = preg_match($expression, $target->getName()) === 1;
     }
     if ($regex->isNegatedRegex()) {
         $expression = str_replace(State::REGEX_PREFIX_NEGATED, '', $regex->getName());
         $matches = preg_match($expression, $target->getName()) !== 1;
     }
     return $matches;
 }
Ejemplo n.º 4
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);
 }
Ejemplo n.º 5
0
 /**
  * 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;
 }
Ejemplo n.º 6
0
 /**
  * @test
  * @group regex
  */
 public function shouldReturnArrayOfMatchedStates()
 {
     $a = new State('a');
     $b = new State('ab');
     $c = new State('ba');
     $d = new State('abracadabra');
     $e = new State('action-hero');
     $f = new State('action-bad-guy');
     $g = new State('ac');
     $targets = array($a, $b, $c, $d, $e, $f, $g);
     $regex = new State('regex:/.*/');
     $this->assertEquals($targets, Utils::getAllRegexMatchingStates($regex, $targets));
     $regex = new State('regex:/^a.*/');
     $this->assertEquals(array($a, $b, $d, $e, $f, $g), Utils::getAllRegexMatchingStates($regex, $targets));
     $regex = new State('regex:/^a.+/');
     $this->assertEquals(array($b, $d, $e, $f, $g), Utils::getAllRegexMatchingStates($regex, $targets));
     $regex = new State('regex:/^a.*a.+$/');
     $this->assertEquals(array($d, $f), Utils::getAllRegexMatchingStates($regex, $targets));
     $regex = new State('regex:/^ac.*-.+$/');
     $this->assertEquals(array($e, $f), Utils::getAllRegexMatchingStates($regex, $targets));
     $regex = new State('ac');
     $this->assertFalse($regex->isRegex());
     $this->assertEquals(array($g), Utils::getAllRegexMatchingStates($regex, $targets), 'non regex state');
 }
 /**
  * @test
  * @group regex
  */
 public function shouldNotReturnRegexState()
 {
     $name = 'rege:.*';
     $regex = new State($name);
     $this->assertFalse($regex->isRegex());
     $this->assertFalse($regex->isNormalRegex());
     $this->assertFalse($regex->isNegatedRegex());
 }
Ejemplo n.º 8
0
 /**
  * {@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);
 }
Ejemplo n.º 9
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);
 }
 /**
  *
  * @param State $state_from
  * @param State $state_to
  * @param string $event
  *            optional: an event name by which this transition can be
  *            triggered
  * @param string $rule
  *            optional: one or more fully qualified Rule (sub)class name(s)
  *            to check to see if we are allowed to transition.
  *            This can actually be a ',' seperated string of multiple rules
  *            that will be applied as a chained 'and' rule.
  * @param string $command
  *            optional: one or more fully qualified Command (sub)class
  *            name(s) to execute for a transition.
  *            This can actually be a ',' seperated string of multiple
  *            commands that will be executed as a composite.
  * @param callable $callable_guard
  *            optional: a php callable to call. eg: "function(){echo 'closure called';};"
  * @param callable $callable_transition
  *            optional: a php callable to call. eg: "izzum\MyClass::myStaticMethod"
  */
 public function __construct(State $state_from, State $state_to, $event = null, $rule = self::RULE_EMPTY, $command = self::COMMAND_EMPTY, $callable_guard = self::CALLABLE_NULL, $callable_transition = self::CALLABLE_NULL)
 {
     $this->state_from = $state_from;
     $this->state_to = $state_to;
     $this->setRuleName($rule);
     $this->setCommandName($command);
     $this->setGuardCallable($callable_guard);
     $this->setTransitionCallable($callable_transition);
     // setup bidirectional relationship with state this transition
     // originates from. only if it's not a regex or final state type
     if (!$state_from->isRegex() && !$state_from->isFinal()) {
         $state_from->addTransition($this);
     }
     // set and sanitize event name
     $this->setEvent($event);
 }
 /**
  * @test
  */
 public function shouldHaveBidirectionalAssociation()
 {
     $from = new State('a');
     $to = new State('b');
     $transition = new Transition($from, $to);
     $this->assertTrue($from->hasTransition($transition->getName()));
     $this->assertFalse($to->hasTransition($transition->getName()), 'not on an incoming transition');
 }