SitePoint Sponsor

User Tag List

Results 1 to 6 of 6
  1. #1
    SitePoint Evangelist
    Join Date
    Mar 2005
    Posts
    423
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Front Controller Delegating to Page Controller

    I was wondering if someone could have a look at what i'm trying to achieve, and let me know if i'm on the right track.

    I like the front controller, but have found so far it works best in collaboration with the command pattern. What i'm trying to do with the following is have the front controller delegate to a more specific controller.

    The "abstract" (php4 i'm afraid) controller class, and its implementors:
    PHP Code:
    //File: clsBaseController.php
    <?php
        
    class clsBaseController
        
    {
            var 
    $Request;
            
            function 
    run($request)
            {
                die(
    "Abstract method run called from " __FILE__);   
            }
            
            function 
    UnknownAction()
            {
                die(
    "Abstract method UnknownAction called from " __FILE__);   
            }
            
            function 
    setRequest(&$Request)
            {
                
    $this->Request =& $Request;   
            }
        }
    ?>
    PHP Code:
    //File: clsIndexController.php
    <?php
        
    require_once(ROOT_PATH "lib/controllers/clsBaseController.php");
        class 
    clsIndexController extends clsBaseController
        
    {
            function 
    UnknownAction(){
                echo 
    "method UnknownAction called in "__FILE__;
                
    //just some links for demonstration
                
    echo '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=Index">Default Index Action</a>'
                echo 
    '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=Index&action=bar">Index Bar</a>'
                echo 
    '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=User&action=Edit">Edit User</a>'
                echo 
    '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=User&action=Add">Add User</a>';
                echo 
    '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=User">Default User Action</a>';
                echo 
    '<br /><a href = "' $_SERVER['PHP_SELF'] . '?controller=Evil&action=DeleteEveryone">Evil Injection URL</a>';
            }
            
            function 
    foo(){
                echo 
    'method foo called in ' __FILE__;   
            }
            
            function 
    bar(){
                echo 
    'method bar called in ' __FILE__;
            }
        }
    ?>
    PHP Code:
    //file: clsUserController.php
    <?php
        
    require_once(ROOT_PATH "lib/controllers/clsBaseController.php");
        class 
    clsUserController extends clsBaseController
        
    {        
            function 
    UnknownAction(){
                echo 
    "method unknown action called in " __FILE__;
            }
            
            function 
    Add(){
                echo 
    'method Add called in ' __FILE__;   
            }
            
            function 
    Edit(){
                echo 
    'Method Edit called in ' __FILE__;
            }
        }
    ?>
    Now the front controller:
    PHP Code:
    //file: clsFrontController.php
    <?php
      
    class clsFrontController
        
    {
            function 
    clsFrontController()
            {}                                             
            
            function 
    run(){
                
    $this->init();
                
    $this->handleRequest();
            }
            
            function 
    init(){
                require_once(
    CLASSES 'clsRequest.php');
                
    $Request =& clsRequestData::getInstance();

                require_once(
    CLASSES 'clsRegistry.php');            
                
    $Registry =& clsRegistry::getInstance();
                
    $Registry->addEntry('Request', &$Request);
            }
            
            function 
    handleRequest(){
                require_once(
    'clsControllerResolver.php');
                
    $ControllerResolver = &new clsControllerResolver();

                
    $Registry =& clsRegistry::getInstance();
                
    $Request =& $Registry->getEntry('Request');

                
    $controller $ControllerResolver->getController($Request);
                
    $action $ControllerResolver->getAction($Request);
                
    $controller->setRequest($Request);
                
    $controller->$action();
            }        
        }
    ?>
    the front controller uses (what i call) a "ControllerResolver" to map the request parameters to the proper controller:
    PHP Code:
    //file: clsControllerResolver.php
    <?php
        
    class clsControllerResolver
        
    {
        var 
    $DefaultController;
            var 
    $DefaultAction;
            var 
    $ChosenController;
            
            function 
    clsControllerResolver(){
                 require_once(
    ROOT_PATH '/lib/controllers/clsIndexController.php');
                 
    $this->DefaultController =& new clsIndexController;
                 
    $this->DefaultAction "UnknownAction";
            }
            
            function &
    getController(&$Request){
                
    $controller $Request->getProperty('controller');
                if(!
    $controller){
                    return 
    $this->DefaultController;
                }
                
                
    //apply filters/ decorate request
                
                
    $controller str_replace(array('.''/'), ''$controller);
                
    $filepath ROOT_PATH 'lib/controllers' DIRECTORY_SEPARATOR 'cls' $controller 'Controller.php';
                
    $classname 'cls'.$controller 'Controller';

                if(!
    file_exists($filepath)){
                    echo(
    'Controller '.$classname.' not found<br />');
                    return 
    $this->DefaultController;
                }
                
                
    //file exists ok, lets see if it has an appropriate class
                
    require_once($filepath);
                
                if(
    class_exists($classname)){
                    
    $this->ChosenController $classname;
                    return new 
    $classname;
                }else{
                    echo (
    'Controller '.$controller.' is not a recognised controller<br />');
                    return 
    $this->DefaultController;
                }
            }
            
            function 
    getAction($Request)
            {
                if (
    strlen($this->ChosenController) == 0){
                    
    $controller $this->DefaultController;
                }else{
                    
    $controller =$this->ChosenController;
                }
                
                
    $methods = array();
                
    $methods get_class_methods($controller);
                
                if (
    in_array($Request->getProperty('action'), $methods) === true)
                {
                    return 
    $Request->getProperty('action');      
                }
                else
                {
                    return 
    $this->DefaultAction;
                }
            }           
    }    
    ?>
    Usage:
    PHP Code:
        require_once('../config.php');
        require_once(
    ROOT_PATH 'lib/controllers/clsFrontController.php');
        
        
    $FrontController =& new clsFrontController();
        
    $FrontController->run(); 

    So let me briefly step through what happens: the url of eg myserver.com would generate the default controller, named IndexController. Likewise, if you specify the IndexController in the request, but don't mention any specific action on that controller, the default action, named UnknownAction, will be fired eg:
    mysite.com
    mysite.com?controller=Index
    mysite.com?controller=Index&action=DeleteDB (method doesn't exist, so UnknownAction will be triggered).


    If the design is sound, i'm hoping to use mod_rewrite to make the urls look like eg: mysite.com/user/add

    If you've got this far, thanks for your time.

    Any advice would be greatly appreciated, likewise any samples of the same technique.

  2. #2
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Not got the time to look any deeper at your classes but I have two points,

    1) Remove the init class method from the front controller as in my view this shouldn't be there - doesn't fit the responsibility of the front controller; Instead initialise the registry from outside the front controller - it's statically called anyways, so it's not like you need to create an instance of it, and

    2) In your script, you have the following,

    PHP Code:
    // ...
    $Registry =& clsRegistry::getInstance();
                
    $Request =& $Registry->getEntry('Request'); 
    Again, it's a static class, so is there any real need to create a variable/reference at this point? Somehow I would use it more directly for example,

    PHP Code:
    // ...
    controller $ControllerResolver->getController$request clsRegistry::getInstance() -> getEntry'Request' ) ); 
    The readability isn't effected too much - you can still read the statement and understand what is happening - and you save a few bytes, but just as importantly you are removing a few lines from the class method, which is what you want in the end anyways

    Might seam I'm just picking at things, proberly I suppose, but in a good way

  3. #3
    SitePoint Evangelist
    Join Date
    Mar 2005
    Posts
    423
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for your reply Dr. Don't worry about being too picky, i'm open to any feedback and welcome all opinions.

    Quote Originally Posted by Dr
    The readability isn't effected too much - you can still read the statement and understand what is happening - and you save a few bytes, but just as importantly you are removing a few lines from the class method, which is what you want in the end anyways
    Yes, thats much cleaner, although i'm using php4 and i don't think it will allow the following syntax :
    PHP Code:
    $request clsRegistry::getInstance()->getEntry'Request' 
    which is why everything is a bit more verbose.

    While on the subject of the front controller, i was wondering if the following was ok:
    PHP Code:
    $controller $ControllerResolver->getController($Request);
    $action $ControllerResolver->getAction($Request);
    $controller->setRequest($Request);
    $controller->$action(); 
    ... to decide which action to run on which controller. Is it the responsibility of the front controller to do this? Or should the selected page controller store as a private variable the method that needs to be executed, and the front controller simply calls an "execute()" method on the selected page controller?

  4. #4
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by skinny monkey
    While on the subject of the front controller, i was wondering if the following was ok:
    PHP Code:
    $controller $ControllerResolver->getController($Request);
    $action $ControllerResolver->getAction($Request);
    $controller->setRequest($Request);
    $controller->$action(); 
    ... to decide which action to run on which controller. Is it the responsibility of the front controller to do this? Or should the selected page controller store as a private variable the method that needs to be executed, and the front controller simply calls an "execute()" method on the selected page controller?
    The way I do it is:

    PHP Code:
            $request = new Request();
            
    $response = new Response();
            
            
    $controller Router::map($request);
            
            if (
    is_a($controller'Controller')) {
                
    $response $controller->process($request$response);
            } else {
                
    trigger_error('Mapper returned an empty controller, make sure your controller extends the controller base class'E_USER_WARNING);
                return 
    false;
            } 
    Which means the controller itself is responsible for deciding which action to execute. It was important to me to have all interaction go through one method, as each controller has it's own filter chain, etc...

    edit:

    Before anyone says anything, I'm going to be removing that static call soon

  5. #5
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > although i'm using php4 and i don't think it will allow the following syntax

    It doesn't? Been a long time since I used PHP4 so I just thought the lesser version worked the same. But to your question, here is how I do it at the moment,

    PHP Code:
    try {
            
    Map::getInstance() -> load$_SERVER['DOCUMENT_ROOT'].'/protected/routes/routes.xml' );
            
            
    Component::set'logger' );
            
    Component::set'configs' );
            
    Component::set'request' );
            
            
    Component::get'iconfiguration' ) -> import( new Parameters$configs ) );
            
    Component::set'mysqlconnection' );
            
            
    $dispatcher = new PageDispatcher();
            
    $dispatcher -> setControllerDirectory$_SERVER['DOCUMENT_ROOT'].'/protected/controllers/' );
            
    $dispatcher -> dispatch();
        
        } catch( 
    Exception $e ) {} 
    And then you have this,

    PHP Code:
    final class PageDispatcher extends Dispatcher {
            protected 
    $privileges = array(
                
    'index'        =>    'private' );
                
            public function 
    __construct() {}
            public function 
    indexAction() {
                include_once( 
    $this -> getControllerDirectory().'controller.php' );
                return 
    Page::getInstance() -> index();
            }
        } 
    The actual Dispatcher looks more like this,

    PHP Code:
    abstract class Dispatcher {
            protected 
    $rules = array();
            protected 
    $directory;
            protected 
    $router;
            
            public function 
    __construct() {}
            public function 
    attachRule$rule ) {
                
    $this -> rules[] = $rule;
            }
            
            public function 
    setControllerDirectory$directory ) {
                
    $this -> directory $directory;
            }
            
            public function 
    processIRequestHandler $request$action ) {
                return 
    $this -> $action();
            }
            
            public function 
    isDispatchableIRequestHandler $request ) {
                if( 
    method_exists$this$this -> formatActionName$request -> get'action' ) ) ) ) {
                    return 
    true;
                }
                return 
    false;
            }
            
            public function 
    dispatch() {
                
    $this -> getRouter() -> route$request Component::get'irequesthandler' ), $this );
                
                
    $action $this;
                foreach( 
    $this -> rules as $rule ) {
                    if( 
    $rule instanceof IActionRule ) {
                        if( !
    $this -> isAllowed$request -> get'action' ) ) ) {
                            
    $action $rule -> applyRule$action );
                        }
                    }
                }
                
                
    $page $action -> process$request$this -> formatActionName$request -> get'action' ) ) );
                
    $response = new Response();
                
    $response -> render$page );
            }
            
            protected function 
    getRouter() {
                if( !
    $this -> router instanceof Router ) {
                    
    $this -> router = new Router();
                }
                return 
    $this -> router;
            }
            
            protected function 
    formatActionName$unformatted ) {
                return (string) 
    strtolower$unformatted ).'Action';
            }
            
            protected function 
    getControllerDirectory() {
                return 
    $this -> directory;
            }
            
            protected function 
    isAllowed$action ) {
                return 
    array_key_exists$action$this -> privileges ) && $this -> privileges[$action] == 'public';
            }
            
            abstract public function 
    indexAction();
        } 
    I took some inspiration from the Zend Framework, but instead of having just the one index.php file to route all actions, I have separate, individual files to route specific actions instead, so instead of having both the controller and the action in the url, I only need the action.

    To me, this gives a cleaner URL. I would suggest that you take a look at Routes as this can help greatly in regards to how you map the request to the controller in question

  6. #6
    Non-Member melancholic's Avatar
    Join Date
    Nov 2004
    Location
    Australia
    Posts
    447
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hi there,

    Quote Originally Posted by skinny monkey
    While on the subject of the front controller, i was wondering if the following was ok:
    PHP Code:
      $controller $ControllerResolver->getController($Request);
      
    $action $ControllerResolver->getAction($Request);
      
    $controller->setRequest($Request);
      
    $controller->$action(); 
    ... to decide which action to run on which controller. Is it the responsibility of the front controller to do this? Or should the selected page controller store as a private variable the method that needs to be executed, and the front controller simply calls an "execute()" method on the selected page controller?
    To be quite honest, I never had this dillema myself I've always assumed that the front controller would select the application controller and it would be up to the Application Controller to choose the command and the view which makes up the flow of the application.

    You have some hints of the Command Pattern in your example (I do this as well, but I will be refactoring soon, it seems our learning curves are almost parallel to each other ), you've included what would be the "command" objects as part of the Application Controller in the form of methods.

    So your foo(), bar() and unknownAction() methods within your sample front controller are actually commands that are executed when the Application Controller is run.

    From examples I've reviewed regarding the Front Controller and Application Controller the action is handled within the Application Controller either through encapsulating the functionality within a method, or by forming a relationship with a ActionDispatcher/Resolver of some description much like the way your ControllerResolver mapped a controller to the URI.

    Front Controller initialises and uses the request object to determine what application controller to run / maybe define the previously mentioned dispatcher or resolver which may be passed in to the App Controller. (If you consider the dispatcher as a separate machine and not part of the Application Controller, like I said you could use this as a method and refactor later on).

    Application controller uses the Command/Action Dispatcher to carry out a command/action.

    So there it is, yet another long winded post
    Granted that it's a little simplistic, but hopefully I helped shed some light.

    Good luck with it.


    Regards,


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
  •