targetState * * @var array */ private $_rules = array(); /** * List of entry actions * Each action executes when entering the state * * [state] => action * * @var array */ private $_entryActions = array(); /** * List of exit actions * Each action executes when exiting the state * * [state] => action * * @var array */ private $_exitActions = array(); /** * List of input actions * Each action executes when entering the state * * [state][input] => action * * @var array */ private $_inputActions = array(); /** * List of input actions * Each action executes when entering the state * * [state1][state2] => action * * @var array */ private $_transitionActions = array(); /** * Finite State machine constructor * * $states is an array of integers or strings with a list of possible machine states * constructor treats fist list element as a sturt state (assignes it to $_current state). * It may be reassigned by setState() call. * States list may be empty and can be extended later by addState() or addStates() calls. * * $inputAphabet is the same as $states, but represents input alphabet * it also may be extended later by addInputSymbols() or addInputSymbol() calls. * * $rules parameter describes FSM transitions and has a structure: * array( array(sourseState, input, targetState[, inputAction]), * array(sourseState, input, targetState[, inputAction]), * array(sourseState, input, targetState[, inputAction]), * ... * ) * Rules also can be added later by addRules() and addRule() calls. * * FSM actions are very flexible and may be defined by addEntryAction(), addExitAction(), * addInputAction() and addTransitionAction() calls. * * @param array $states * @param array $inputAphabet * @param array $rules */ public function __construct($states = array(), $inputAphabet = array(), $rules = array()) { $this->addStates($states); $this->addInputSymbols($inputAphabet); $this->addRules($rules); } /** * Add states to the state machine * * @param array $states */ public function addStates($states) { foreach ($states as $state) { $this->addState($state); } } /** * Add state to the state machine * * @param integer|string $state */ public function addState($state) { $this->_states[$state] = $state; if ($this->_currentState === null) { $this->_currentState = $state; } } /** * Set FSM state. * No any action is invoked * * @param integer|string $state * @throws Zend_Search_Exception */ public function setState($state) { if (!isset($this->_states[$state])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('State \'' . $state . '\' is not on of the possible FSM states.'); } $this->_currentState = $state; } /** * Get FSM state. * * @return integer|string $state|null */ public function getState() { return $this->_currentState; } /** * Add symbols to the input alphabet * * @param array $inputAphabet */ public function addInputSymbols($inputAphabet) { foreach ($inputAphabet as $inputSymbol) { $this->addInputSymbol($inputSymbol); } } /** * Add symbol to the input alphabet * * @param integer|string $inputSymbol */ public function addInputSymbol($inputSymbol) { $this->_inputAphabet[$inputSymbol] = $inputSymbol; } /** * Add transition rules * * array structure: * array( array(sourseState, input, targetState[, inputAction]), * array(sourseState, input, targetState[, inputAction]), * array(sourseState, input, targetState[, inputAction]), * ... * ) * * @param array $rules */ public function addRules($rules) { foreach ($rules as $rule) { $this->addrule($rule[0], $rule[1], $rule[2], isset($rule[3])?$rule[3]:null); } } /** * Add symbol to the input alphabet * * @param integer|string $sourceState * @param integer|string $input * @param integer|string $targetState * @param Zend_Search_Lucene_FSMAction|null $inputAction * @throws Zend_Search_Exception */ public function addRule($sourceState, $input, $targetState, $inputAction = null) { if (!isset($this->_states[$sourceState])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined source state (' . $sourceState . ').'); } if (!isset($this->_states[$targetState])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined target state (' . $targetState . ').'); } if (!isset($this->_inputAphabet[$input])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined input symbol (' . $input . ').'); } if (!isset($this->_rules[$sourceState])) { $this->_rules[$sourceState] = array(); } if (isset($this->_rules[$sourceState][$input])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Rule for {state,input} pair (' . $sourceState . ', '. $input . ') is already defined.'); } $this->_rules[$sourceState][$input] = $targetState; if ($inputAction !== null) { $this->addInputAction($sourceState, $input, $inputAction); } } /** * Add state entry action. * Several entry actions are allowed. * Action execution order is defined by addEntryAction() calls * * @param integer|string $state * @param Zend_Search_Lucene_FSMAction $action */ public function addEntryAction($state, Zend_Search_Lucene_FSMAction $action) { if (!isset($this->_states[$state])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined state (' . $state. ').'); } if (!isset($this->_entryActions[$state])) { $this->_entryActions[$state] = array(); } $this->_entryActions[$state][] = $action; } /** * Add state exit action. * Several exit actions are allowed. * Action execution order is defined by addEntryAction() calls * * @param integer|string $state * @param Zend_Search_Lucene_FSMAction $action */ public function addExitAction($state, Zend_Search_Lucene_FSMAction $action) { if (!isset($this->_states[$state])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined state (' . $state. ').'); } if (!isset($this->_exitActions[$state])) { $this->_exitActions[$state] = array(); } $this->_exitActions[$state][] = $action; } /** * Add input action (defined by {state, input} pair). * Several input actions are allowed. * Action execution order is defined by addInputAction() calls * * @param integer|string $state * @param integer|string $input * @param Zend_Search_Lucene_FSMAction $action */ public function addInputAction($state, $inputSymbol, Zend_Search_Lucene_FSMAction $action) { if (!isset($this->_states[$state])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined state (' . $state. ').'); } if (!isset($this->_inputAphabet[$inputSymbol])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined input symbol (' . $inputSymbol. ').'); } if (!isset($this->_inputActions[$state])) { $this->_inputActions[$state] = array(); } if (!isset($this->_inputActions[$state][$inputSymbol])) { $this->_inputActions[$state][$inputSymbol] = array(); } $this->_inputActions[$state][$inputSymbol][] = $action; } /** * Add transition action (defined by {state, input} pair). * Several transition actions are allowed. * Action execution order is defined by addTransitionAction() calls * * @param integer|string $sourceState * @param integer|string $targetState * @param Zend_Search_Lucene_FSMAction $action */ public function addTransitionAction($sourceState, $targetState, Zend_Search_Lucene_FSMAction $action) { if (!isset($this->_states[$sourceState])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined source state (' . $sourceState. ').'); } if (!isset($this->_states[$targetState])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('Undefined source state (' . $targetState. ').'); } if (!isset($this->_transitionActions[$sourceState])) { $this->_transitionActions[$sourceState] = array(); } if (!isset($this->_transitionActions[$sourceState][$targetState])) { $this->_transitionActions[$sourceState][$targetState] = array(); } $this->_transitionActions[$sourceState][$targetState][] = $action; } /** * Process an input * * @param mixed $input * @throws Zend_Search_Exception */ public function process($input) { if (!isset($this->_rules[$this->_currentState])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('There is no any rule for current state (' . $this->_currentState . ').'); } if (!isset($this->_rules[$this->_currentState][$input])) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('There is no any rule for {current state, input} pair (' . $this->_currentState . ', ' . $input . ').'); } $sourceState = $this->_currentState; $targetState = $this->_rules[$this->_currentState][$input]; if ($sourceState != $targetState && isset($this->_exitActions[$sourceState])) { foreach ($this->_exitActions[$sourceState] as $action) { $action->doAction(); } } if (isset($this->_inputActions[$sourceState]) && isset($this->_inputActions[$sourceState][$input])) { foreach ($this->_inputActions[$sourceState][$input] as $action) { $action->doAction(); } } $this->_currentState = $targetState; if (isset($this->_transitionActions[$sourceState]) && isset($this->_transitionActions[$sourceState][$targetState])) { foreach ($this->_transitionActions[$sourceState][$targetState] as $action) { $action->doAction(); } } if ($sourceState != $targetState && isset($this->_entryActions[$targetState])) { foreach ($this->_entryActions[$targetState] as $action) { $action->doAction(); } } } public function reset() { if (count($this->_states) == 0) { require_once 'Zend/Search/Exception.php'; throw new Zend_Search_Exception('There is no any state defined for FSM.'); } $this->_currentState = $this->_states[0]; } }