SitePoint Sponsor

User Tag List

Results 1 to 8 of 8
  1. #1
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Dependancy Injection & Finite State Machines

    Been playing with some code and just thought I'd share for some feedback.

    Basically involved using create a finite state machine and using a dependency injector for actions, atm it seems very neat.

    PHP Code:

    interface Action
    {
        function 
    execute();
    }

    class 
    Processor implements Action
    {
        protected 
    $injector;
        protected 
    $transitions;

        const 
    START 0;        // starting state

        
    function __construct(DependancyInjector $injector)
        {
            
    $this->injector $injector;
            
    $this->transitions = array();
        }
        
        
    /* 
            @param state - is a key, and either $state or $action is the class name for the action implementation
            @param outgoingStates - is an optional array of state => return-value pairs. (Reversed due to PHP index limitations.)
        */
        
    function addState($state, array $outgoingStates null$action null)
        {
            
    $this->injector->registerComponent($stateis_null($action) ? $state $action);
            
    $this->transitions[$state] = $outgoingStates;
        }

        function 
    execute()
        {
            
    $state self::START;
            do
            {
                
    $action $this->injector->getInstance($state);

                
    $r $action->execute();

                
    // translate the current state & return value into the new state.
                
    $states $this->transitions[$state];
                
    $state is_array($states) ? array_search($r$states) : false;
            }
            while (
    $state !== false);
        }

    And an simple looping example..

    PHP Code:

    class LoopState
    {
        public 
    $text '';
    }

    class 
    SimpleLooping extends Processor
    {
        function 
    __construct(DependancyInjector $injector)
        {
            
    parent::__construct($injector);

            
    $this->addState(self::START, array('AppendOneChar' => false'MetMinimumLength' => true), 'MeetsMinimumLength');
            
    $this->addState('AppendOneChar', array(self::START => true));
            
    $this->addState('MetMinimumLength');
        }
    }

    abstract class 
    LoopAction implements Action
    {
        protected 
    $state;
        function 
    __construct(LoopState $state) { $this->state $state; }
    }

    class 
    MeetsMinimumLength extends LoopAction
    {
        function 
    execute() { return strlen($this->state->text) >= 5; }
    }

    class 
    AppendOneChar extends LoopAction
    {
        function 
    execute() { $this->state->text .= chr(mt_rand(ord('a'), ord('z'))); return true; }
    }

    class 
    MetMinimumLength extends LoopAction
    {
        function 
    execute() { echo $this->state->text; }
    }


    $di = new DependancyInjector();
    $di->registerCachedComponent('LoopState');

    $dii = new DependancyInjector($di);
    $loop = new SimpleLooping($dii);

    $loop->execute(); 

  2. #2
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Have you tried applying this for web continuations? I believe we toyed with FSMs in the skeleton controller thread, but the idea didn't gain much popularity and was consequently never fleshed out. I'd be interested if you could take this idea further.

    I think the most obvious application of this is a multi-step form, with validators to each stage and so on. It doesn't feel like worth the trouble for 2-page interactions, though, not at all. Can you think of other places where this could be of use?

  3. #3
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Havent tried much with it yet. Just literally coded & posted it.

    There is a couple of weakeness need solving first, i think, one that Processor::execute() needs to be able to return some value. So then can set up hierarchies of state machines.

  4. #4
    SitePoint Evangelist
    Join Date
    Aug 2004
    Posts
    428
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    umm i'm actually going be working on a windows form app which i'm going model as a state machine.

    I'll like to see how this thread plays out.

    i have no idea what dependency injector is.. would you mind describing what it means to you?

    I know gof has a state pattern.. what would be different here?

    From reading the definition of chain of responsibility.. i would probably use that approach to model an FSM. As once a state accepts the context object it will no longer forward it to the other states.


    where $objContext is new input and reference to present state ... each state in the chain would then decide if it meets the requirements to be the next state.

    onsubmit: or some event that would cause state change
    $chain->accept($objContext);


    in my winform app.. here is the responsbility i'm going put in each state:
    default output,
    validation on form controls it placed in the window,
    result output,
    remove ability to remove all items it placed in the window.


    once the next state accepts the context object it will $this->objContext->present_state->remove();
    $this->objContext->setPresentState(State $this)
    $this->render(); //render itself on the screen

    Leblanc Meneses
    Last edited by leblanc; Dec 22, 2006 at 13:29.

  5. #5
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by leblanc
    i have no idea what dependency injector is.. would you mind describing what it means to you?
    http://www.martinfowler.com/articles/injection.html

  6. #6
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > From reading the definition of chain of responsibility..

    Yes... That is what I'm using at the moment, here take a peek for yourself

    PHP Code:
    final class InitialLogInFormActionHandler extends FormActionHandler {
            public function 
    __construct() {
                
    $this -> initialise();
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodes ) {
                if( 
    $this -> validate$request Component::get'request' ), Component::get'ilogger' ) ) ) {
                    
    $this -> successor -> execute$nodes );
                } else {
                    
    // display initial form from here
                
    }
            }
            
            protected function 
    initialise() {
                
    $this -> attachRule( new PostRequestRule() );
            }
        }
        
        final class 
    SuccessLogInFormActionHandler extends FormActionHandler {
            public function 
    __construct() {
                
    $this -> initialise();
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodes ) {
                if( !
    $this -> validate$request Component::get'request' ), Component::get'ilogger' ) ) ) {
                    
    $this -> successor -> execute$nodes );
                } else { 
                    
    $login LogInFactory::create(); 
                    if( 
    $user $login -> searchByUsername$request -> get'username' ) ) ) {
                        if( 
    is_array$user ) && $user['password'] == (string) md5$request -> get'password' ) ) ) {
                            
    // user has proper login credentials
                            
    $signon time();
                            
    $login -> acknowledgeSignon$user['username'], $signon );
                            
    $request -> getSession() -> set'username'$user['username'] );
                            
    $request -> getSession() -> set'password'$user['password'] );
                            
                            
    header'location:http://...' );
                            exit;    
                        }
                    }
                    
    header'location:http://...' );
                    exit;
                }
            }
            
            protected function 
    initialise() {
                
    $this -> attachRule( new RegExpressionRule'username''/^[a-z]+$/' ) );
                
    $this -> attachRule( new MaximumSizeRule'username'32 ) );
                
    $this -> attachRule( new RequiredRule'username' ) );
                
    $this -> attachRule( new RegExpressionRule'password''/^[a-z]+$/' ) );
                
    $this -> attachRule( new MaximumSizeRule'password'32 ) );
                
    $this -> attachRule( new RequiredRule'password' ) );
            }
        }
        
        final class 
    FailureLogInFormActionHandler extends FormActionHandler {
            public function 
    __construct() {
                
    $this -> id 'body';
            }
            
            public function 
    executeIDataspace $nodes ) {
                
    // handle failure form display from here
            
    }
        } 

  7. #7
    SitePoint Evangelist
    Join Date
    Aug 2004
    Posts
    428
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    so your states would be:
    Initial Loggin [back to self until its able to move to success state]
    Success Log In [contents of admin section]
    Failure Log in [back to initial loggin]

    What would be the api?

    $chain = new InitialLogin(new SuccessLog( new FailureLog( new LeafNode())))

    i'm curious to see how cor is ended.
    $chain->process($context);

    leafnode just sees that it contains no references to a live object and doesn't forward the request

    I just wonder that for such a simple example.. i don't see the benefit

    if(!$auth->isValid()===true){
    code failure log work, show login form again
    $f = new FailureLog();
    $f->execute();
    }else{
    redirect to welcome admin
    $f = new SuccessLog();
    $f->execute();
    }


    The problem with statemachines is they are very tedious to build. The right input and the present state have to be taken into account before it moves to the next state.

    an application that requires moving back and forth between states would benefit highly from a statemachine.. however this example i feel requires to much initial effort for less flexability.

    looks clean tho. and i'm sure it would work

    Leblanc Meneses
    Last edited by leblanc; Dec 22, 2006 at 13:29.

  8. #8
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > What would be the api?

    PHP Code:
    // this is the decorator pattern, you have used
    $chain = new InitialLogin( new SuccessLog( new FailureLog( new LeafNode() ) ) ); 
    The API for the CoR is simular to this for example,

    PHP Code:
    include_once( 'interfaces/actionhandler.php' );
        
        
    /**
         * @package     action handler
         * @author         les quinn (nukebaghdad@yahoo.co.uk)
         * @abstract
         * @since         1.0
         */
         
        
    abstract class ActionHandler implements IActionHandler {
            protected 
    $id;
            protected 
    $parent null;
            protected 
    $children = array();
            
            public function 
    __construct() {}
            public function 
    getId() {
                return 
    $this -> id;
            }
            
            public function 
    hasChildren() {
                return 
    count$this -> children );
            }
            
            public function 
    getChildren() {
                return 
    $this -> children;
            }
            
            public function 
    attachIComposite $composite ) {
                
    $this -> children[$composite -> getId()] = $composite;
            }
            
            abstract public function 
    executeIDataspace $nodes );
        }
        
        
    /**
         * @package     forms action handler
         * @author         les quinn (nukebaghdad@yahoo.co.uk)
         * @abstract
         * @since         1.0
         */
         
        
    abstract class FormActionHandler implements IActionHandler {
            protected 
    $id;
            protected 
    $parent null;
            protected 
    $rules = array();
            protected 
    $children = array();
            protected 
    $successor null;
            
            public function 
    __construct() {}
            public function 
    getId() {
                return 
    $this -> id;
            }
            
            public function 
    hasChildren() {
                return 
    count$this -> children );
            }
            
            public function 
    getChildren() {
                return 
    $this -> children;
            }
            
            public function 
    attachIComposite $composite ) {
                
    $this -> children[$composite -> getId()] = $composite;
            }
            
            public function 
    setHandlerIActionHandler $handler ) {
                
    $this -> successor $handler;
            }
            
            public function 
    attachRuleIFormRule $rule ) {
                
    $this -> rules[] = $rule;
            }
            
            protected function 
    validateIDataspace $request$logger ) {
                
    $validation true;
                foreach( 
    $this -> rules as $rule ) {
                    
    $validation $rule -> validate$request$logger ) && $validation;
                }
                return 
    $validation;
            }
            
            abstract public function 
    executeIDataspace $nodes );
        }
        
        
    /**
         * @package     action handler decorator
         * @author         les quinn (nukebaghdad@yahoo.co.uk)
         * @abstract
         * @since         1.0
         */
         
         
    abstract class DecorateActionHandler implements IActionHandler {
            protected 
    $id;
            protected 
    $parent null;
            protected 
    $children = array();
            protected 
    $action_handler null;
            
            public function 
    __constructIActionHandler $action_handler ) {
                
    $this -> action_handler $action_handler;
            }
            
            public function 
    getId() {
                return 
    $this -> action_handler  -> getId();
            }
            
            public function 
    hasChildren() {
                return 
    $this -> action_handler -> hasChildren();
            }
            
            public function 
    getChildren() {
                return 
    $this -> action_handler -> getChildren();
            }
            
            public function 
    attachIComposite $composite ) {
                
    $this -> action_handler -> attach$composite );
            }
            
            abstract public function 
    executeIDataspace $nodes );
        } 
    Basically all you need in the API is this,

    PHP Code:
    // ...
    public function setHandler$handler ); 
    To use it, I use something like this for example,

    PHP Code:
    $page = new PageActionHandler();
    $page -> attach$body = new InitialFormActionHandler() );
    $page -> attach$head = new HeadActionHandler() );
    // ...
    $body -> setHandler$success = new SuccessFormActionHandler() );
    $success -> setHandler( new FailureFormActionHandler() );
    // and your away 
    But generally it is thought that with form processing that all you need is 3 steps, but in a lot of cases you actually need 4 if you are involving a data source. The way that the abstract classes are set up, I can go further and use another fourth handler to validate the database insert or update.

    So for example, if the database returns that an email is duplicate for a new insertion, then the validation within the fourth handler will pick this up, and return a failure as expected; It just means that you can abstract that responsibility out of the success handler, which gives cleaner script at the end of the day...

    The validation doesn't know about nor doesn't care about the rules, so you can attach whatever rules you require, on the per handler basis. As for state machines, well that's something different. Ren, sorry for taking your thread off topic, didn't set out to do so.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •