SitePoint Sponsor

User Tag List

Results 1 to 5 of 5
  1. #1
    SitePoint Member
    Join Date
    Sep 2003
    Location
    Sydney, Australia
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Router/Dispatch Design Issue.

    Hi guys, it's been a long while since I've been to these forums but I'm in a bit of a predicament at the moment trying to resolve an issue of who is responsible for what (between my Router & Dispatcher).

    I have a working Router implemented which takes a series of Route patterns and matches them against a given URL. The first to match successfully will have my Router return a Command object full of all the goodies my Dispatcher needs (name of controller, action and any other parameters).

    At the moment this Command object would be passed to the Dispatcher to initiate the controller and dispatch the action.

    It is however right at this same point that I am a little stuck with how to handle things.

    I would very much like to have the ability (as most frameworks do) to default back to a default Module, Controller & Action if the Route returned by my Router is not dispatchable as is.

    Here is where the problem lies. What (between the Router & the Dispatcher) do you guys think should be responsible for injecting these defaults?

    On one had I have a Router which in my eyes should simply match patterns to urls and hand control to something else when it's done. On the other hand, I don't want the Dispatcher to be playing around with what is (at this stage) considered a valid Route.

    A big part of me want's this to be handled at the Router. I already have plans for another step in the bootstrap process to sit between the Router and Dispatcher but it (this other step) will definitely require knowledge of what is about to be dispatched. However allot of code that I have seen seems to have the Dispatcher attempt to load what the Router has given it, and if it can't the dispatcher itself starts injecting defaults until it can.

    Any thoughts on the subject would be much appreciated. Thanks.

    ps: I have the project hosted on Github if seeing code might make things clearer. I just didn't want to link it here as I haven't posted in a while and didn't want to give the impression I was spamming.

  2. #2
    SitePoint Wizard gRoberts's Avatar
    Join Date
    Oct 2004
    Location
    Birtley, UK
    Posts
    2,439
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I've been playing with my own framework since using CodeIgniter, which has helped a great deal.

    Using regex, I replace the current route with the new route and values...

    i.e.

    Code Routes:
        $config['source'] = 'QUERY_STRING';
        $config['default_controller'] = 'common';
        $config['default_action'] = 'index';
     
        $routes['sample/route/(:num)/(:any)'] = 'converted/to/$1/$2/$1';

    Code Router:
    class RouteHandler
        {
            private $Routes = array();
            public function __construct()
            {
                // load in the route configuration
                ConfigurationHandler::$Instance->LoadConfig('route');
     
                // although we have already loaded the route
                // configuration file, we need the $routes array
                // from it. We'll find a better way to handle this
                // soon.
                if(file_exists(APP . 'config' . DS . 'route.php'))
                {
                    require APP . 'config' . DS . 'route.php';
                    if(isset($routes))
                    {
                        $this->Routes = $routes;
                    }
                }
            }
     
            public function Route($path = null)
            {
                // if the path we send across is null
                // get it from the URL
                if(is_null($path))
                {
                    // get the key to use against $_SERVER
                    // from the configuration
                    $Source = ConfigurationHandler::$Instance->GetItem('source', 'QUERY_STRING', 'route');
                    // get the value from $_SERVER using the key
                    $path = (isset($_SERVER[$Source]) ? $_SERVER[$Source] : '');
                }
                // prepare the route
                $path = $this->prepareRoute(strpos($path, 'path=') == 0 ? substr($path, 5) : $path);
                $parts = explode('/', $path);
     
                // get the controller from the path, 
                // or default to a value found in the config
                $controller = ucfirst((empty($parts) || empty($parts[0]) ? ConfigurationHandler::$Instance->GetItem('default_controller', '', 'route') : array_shift($parts)));
                // get the action from the path,
                // or default to a value found in the config
                $action = (empty($parts) || empty($parts[0]) ? ConfigurationHandler::$Instance->GetItem('default_action', 'index', 'route') : array_shift($parts));
                // whats left are the parameters
                $params = $parts;
     
                // check to see if the controller exists
                if(file_exists(APP . 'controllers' . DS . $controller . '.php'))
                {
                    require_once(APP . 'controllers' . DS . $controller . '.php');
                }
                else
                {
                    die('Unable to locate the required controller: ' . $controller);
                }
                // create a instance of the controller
                $ControllerInstance = new $controller();
                // check to see if the action exists in the controller
                if(!method_exists($ControllerInstance, $action))
                {
                    die('Unable to find the required action: ' . $action . ' within the controller: ' . $controller);
                }
                // call the action inside the controller
                call_user_func_array(array($ControllerInstance, $action), $params);
            }
     
            private function prepareRoute($Path)
            {
                // go through each of our results
                foreach ($this->Routes as $key => $val)
                {
                    // replace our friendly code with Regex code
                    $key = str_replace(':any', '.+', str_replace(':num', '[0-9]+', $key));                
                    // if it matches
                    if (preg_match('#^'.$key.'$#', $Path))
                    {
                        // replace the current url with the routed url
                        if (strpos($val, '$') !== FALSE AND strpos($key, '(') !== FALSE)
                        {
                            $val = preg_replace('#^'.$key.'$#', $val, $Path);
                        }
                        return $val;
                    }
                }
                return $Path;
            }
        }

    Hope this helps you?


  3. #3
    SitePoint Member
    Join Date
    Sep 2003
    Location
    Sydney, Australia
    Posts
    4
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Cool, but that doesn't answer the question does it.

  4. #4
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    989
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Do you need a command object? Is it anything more than a data structure? Does it actually have behavior?

    Ideally you'll want to be able to call your dispatcher from multiple locations in the application. Removing the dependency of a 'command' object and just passing string parameters makes this far easier.

    Back on topic:

    In short,

    The router should only be concerned with URL matching. It shouldn't know anything about whether specific controllers exist or what's going to happen beyond that point.

    e.g. it will take an input of

    /foo/bar/1/2/3

    and return something like

    PHP Code:
    array(
        
    'controller' => 'foo',
        
    'action' => 'bar',
        
    'params' => array(123)
    ); 
    This, I'm assuming is what you're storing in your command object. I see no reason the router wouldn't have the ability to check whether controllers exist to work out whether to route to the default controller.

    The dispatcher would take a set of as above, potentially controller, action and parameters and call the specified controller.

    You may want to look at these posts:
    http://www.sitepoint.com/forums/php-...er-690594.html

    http://www.sitepoint.com/forums/php-...ng-704816.html

  5. #5
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Have gone for the router returns a closure approach.

    PHP Code:
    class Router
    {
        private 
    $routing;
        private 
    $actions;

        function 
    __construct()
        {
            
    $this->routing = new SplObjectStorage();
            
    $this->actions = new SplObjectStorage();
        }
        
        
    /*
            Add a route to the router.
            
            Internally creates a tree or collection of trees/forest
            
            The children of a given node are only visited if the parent node returns true (ie some match)
        
            $matches - Array of closures that return true or false
            $action - Closure to return if all of the closures in $matches return true
        */
        
    function add(array $matchesClosure $action)
        {
            
    $routing $this->routing;
            foreach(
    $matches as $match)
            {
                if (!
    $routing->contains($match))
                    
    $routing->attach($match, new SplObjectStorage());
                
    $routing $routing[$match];
            }
            
    $this->actions->attach($routing$action);
            return 
    $this;
        }
        
        private function 
    lookup(SplObjectStorage $routingContext $context)
        {
            foreach(
    $routing as $accept)
                if (
    $accept($context))
                {
                    
    // Parent condition returned true, descend into children
                    
    $action $this->lookup($routing[$accept], $context);
                    if (
    $action)
                        return 
    $action;
                }
            
    // Lookup related action given routing..
            
    return $this->actions->contains($routing) ? $this->actions[$routing] : null;
        }

        function 
    lookupAction(Context $context)
        {
            return 
    $this->lookup($this->routing$context);
        }

    The method of setting the default action is just calling the add() method with an empty array as $matches.

    PHP Code:
    $router = new Router();
    $router->add(array(), function() { echo 'Default action'; }); 
    And this is located together with the rest of the router configuration.


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
  •