SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 49

Thread: URL mapping

Hybrid View

  1. #1
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    URL mapping

    I've been working on a mapping sceme for urls to controllers and actions. At this point it works fine. I don't know if I've every seen anyone doing it quite like this so I wanted to get some feedback.

    Example 1

    http://localhost/shop/cart/add/id/43

    Would get a controller called ShopCartController inside of the controller directory like:

    controllers/shop/cart.php

    Then the ShopCartController::add() action is called. The id parameter is available in the request object, which is passed to the action method.


    Example 2

    http://localhost/about

    Would get a controller called DefaultController, because the about controller does not exist, and the default controller has an action called about().

    Example 3

    http://localhost/admin/product/edit/id/54

    This would call the AdminProductController (controllers/admin/product.php) and call it's edit() action. "id" is available as a parameter in the request object.

    -------

    Basically, my URL parser starts at the begining of the path and iterates through each value checking to see if it is a valid controller OR folder OR action

    If it finds a value that is NOT a controller, it checks to see if the last found controller (or default controller if no previous) in the path has an action matching the current path fragment. If it does, the remaining path is paired into parameters. If not, the path from that point is paired into parameters and the default action is called.

    So, if the ShopCartController didn't exist, the ShopController::default() action would be called. At that point, "add" would be a key with a value of "is" and "43" would be a key with no value.

    This all sounds confusing, but it actually works very nicely. If you (anyone) is interested, I'd be happy to post the code so you can see how it works.

    I'm just curious to see what people think of this, and ultimately maybe get some feedback on the code I have.

    - matt

  2. #2
    SitePoint Zealot
    Join Date
    May 2001
    Posts
    193
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    i'd be interested.

  3. #3
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    OK, I've made an archive of what I have. What are your thoughts?

    -matt
    Attached Files Attached Files

  4. #4
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > I'm just curious to see what people think of this, and ultimately maybe get some feedback on the
    > code I have.

    I can't say for good or bad that there would be enough flexibility in the approach that you've taken; I would advice that you take a look at Ruby On Rails to see how that does it though

    On that point, Zend Framework has a proposal at the moment that accomplishes the route approach but be aware that from what testing I have done with the SVN that there are a few bugs I noted...

    I fixed the issues I had, now need to find the time to see if anyone else has had issues via the frameworks mailing list

    Send off a private message to Jarred (Ren) to see what he has to say I suppose?

  5. #5
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Dr Livingston
    I can't say for good or bad that there would be enough flexibility in the approach that you've taken; I would advice that you take a look at Ruby On Rails to see how that does it though

    Send off a private message to Jarred (Ren) to see what he has to say I suppose?
    Hey thanks for looking. What do you mean in terms of flexibility? You mean something like routes? I actually planned on adding that when the need came. I know that this setup would allow for routes without much hassle. If not routes, what do you mean? The thing I like about this is that you can nest your controllers, based off of the content structure of your site. Also, there is no config file.

  6. #6
    SitePoint Zealot Serberus's Avatar
    Join Date
    Oct 2005
    Location
    Herts, UK
    Posts
    113
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Looks good. Regarding verbs in your URLs (add, edit etc), I think it's worth reading this thread, particularly HarryF's post, about REST.

    Update: While I'd respect the URL theme REST outlines I would not go with HTTP Auth due to it's short comings (no logout, plain text storage of login details by the browser, ugly login box that does not fit with the theme of your site).

  7. #7
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Serberus
    Update: While I'd respect the URL theme REST outlines I would not go with HTTP Auth due to it's short comings (no logout, plain text storage of login details by the browser, ugly login box that does not fit with the theme of your site).
    Most browsers allow you to do a logout, although it's not standard. It doesn't bother me a lot.
    Storing plain text is something you often see with other solutions aswell. If you see it as a problem, use the Digest-mode to authenticate with. Or get a SSL certificate.

  8. #8
    SitePoint Zealot Serberus's Avatar
    Join Date
    Oct 2005
    Location
    Herts, UK
    Posts
    113
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Just to bounce ideas around, has anyone ever seen a set up where the dispatcher only examines the first element of the REQUEST_URI to instantiate the appropriate controller, then queries that controller for the rest of the mapping instructions (where the action would be, or any product/article IDs)? Or does this sounds like a horrible way to fragment your mapping/route data through your controllers?

  9. #9
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > What do you mean in terms of flexibility?

    The point of url mapping is that the dependence of selecting the required controller and action are independent (separate) from how the url is composed; By flexibility you do not have this with the approach that you've taken at the moment

    I'm not a Ruby (or Ruby On Rails) fan but God I've got to give due credit to the framework as routes delivers that separation nicely...

  10. #10
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Serberus
    Just to bounce ideas around, has anyone ever seen a set up where the dispatcher only examines the first element of the REQUEST_URI to instantiate the appropriate controller, then queries that controller for the rest of the mapping instructions (where the action would be, or any product/article IDs)? Or does this sounds like a horrible way to fragment your mapping/route data through your controllers?
    Funny ... I'm putting the final touch on a library which does exactly that. http://svn.sourceforge.net/viewcvs.cgi/konstrukt/ if you're interested. (And sorry for hijacking your thread matt)

  11. #11
    SitePoint Zealot Serberus's Avatar
    Join Date
    Oct 2005
    Location
    Herts, UK
    Posts
    113
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    Funny ... I'm putting the final touch on a library which does exactly that. http://svn.sourceforge.net/viewcvs.cgi/konstrukt/ if you're interested. (And sorry for hijacking your thread matt)
    Great, I'll check it out! I figured combining this part in the controller had it's advantages because if you refactor your controller you've easy access to tweak the mapping data as well, no need to modify additional classes such as ActionMapper.

    Sorry for the hijack!

  12. #12
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Following original script taken from SVN proposals of Zend Framework; Matt, does this give you a better idea of routes?

    PHP Code:
    class Route {
            const 
    URL_VARIABLE ':';
            const 
    REGEX_DELIMITER '#';
            const 
    DEFAULT_REGEX '[a-z0-9\-_]+';
            
            private 
    $parts;
            private 
    $defaults = array();
            private 
    $required = array();
            
            public function 
    __construct$route$defaults = array(), $required = array() ) {
                
    $route trim$route'/' ); 
                
    $this -> defaults array_merge$this -> defaults$defaults );
                
    $this -> required array_merge$this -> required$required );
                
                foreach( 
    explode'/'$route ) as $position => $part ) { 
                    if( 
    self::URL_VARIABLE == $part[0] ) { 
                        
    $name substr$part);
                        
    // added fix here
                        // removes : from string, ie
                        // :controller, :action, et al becomes controller, action, et al
                        
    $realname substr$name);
                        
    // following statement uses $realname instead of originally using $name
                        // 13 apr 2006 les quinn
                        
    $regex = isset( $required[$realname] )? $required[$realname]: self::DEFAULT_REGEX;
                        
    $this -> parts[$position] = array( 'name' => $name'regex' => $regex );
                    } else { 
                        
    $this -> parts[$position] = array( 'regex' => preg_quote$partself::REGEX_DELIMITER ) );
                    }
                }
            }
            
            public function 
    match$path ) { 
                
    $values $this -> defaults;
                
    $path explode'/'trim$path'/' ) ); 
                
                foreach( 
    $this -> parts as $position => $part ) { 
                    
    $name = isset( $part['name'] )? $part['name']:null
                    
    $regex self::REGEX_DELIMITER.'^'.$part['regex'].'$'.self::REGEX_DELIMITER.'i';
                    
    // added fix here
                    
    $realname substr$name); 
                    if( !empty( 
    $path[$position] ) && preg_match$regex$path[$position] ) ) {
                        if( !
    is_null$name ) ) { 
                            
    // following statement(s) uses $realname instead of originally using $name
                            // 13 apr 2006 les quinn
                            
    $values[$realname] = $path[$position];
                        }
                    } else if( !
    is_null$name ) && isset( $this -> defaults[$realname] ) ) { 
                        continue;
                    } else { 
                        return 
    false;
                    }
                }
                return 
    $values;
            }
            
            public function 
    assemble$data ) {
                
    // ... not got around to this yet ;)

            
    }
        }
        
        class 
    Router {
            private 
    $routes = array();
            
            public function 
    __construct() {}
            public function 
    addRoute$map$defaults = array(), $required = array() ) {
                
    $this -> routes[] = new Route$map$defaults$required );
            }
            
            public function 
    route() {
                
    $path $_SERVER['REQUEST_URI']; 
                if( 
    strstr$path'?' ) ) {
                    
    $path substr$path0strpos$path'?' ) );
                }
                
                foreach( 
    $this -> routes as $route ) {
                    if( 
    $parameters $route -> match$path ) ) {
                        return 
    $parameters;
                        
    // break;
                    
    }
                }
                return array();
            }
        }
        
        
    $router = new Router();
        
    // archives is static parameter and is the controller portion of the url
        
    $router -> addRoute'archives/:action/date/:yrs/:mth/:day'
            array( 
    'action' => 'tmp_action_index''yrs' => '2006''mth' => '1''day' => '1' ),
            array( 
    'action' => '[a-z]+''yrs' => '[0-9]{4}''mth' => '[0-9]{2}''day' => '[0-9]{2}' ) );
        
        
    // controller, action with static parameter (category), with additional (may be optional) parameter
        
    $router -> addRoute':controller/:action/category/:category'
            
    // defaults are used where there are no matched parameter in url
            
    array( 'controller' => 'index''action' => 'index''category' => 'home' ),
            
    // requires are to determine the matched parameter from url validates against a given regex
            // if no regex given for a specific parameter in this array, a default regex is used instead, ie
            // the default regex ([a-z0-9\-_]+) will be used to validate the category parameter
            
    array( 'controller' => '[a-z]+''action' => '[a-z]+' ) );
        
        
    // controller, action, with static parameter (date), with additional (may be optional) parameters
        
    $router -> addRoute':controller/:action/date/:yrs/:mth/:day'
            
    // uses defaults for year, month and day
            // would use php's date functions to set defaults to current date
            
    array( 'controller' => 'index''action' => 'index''yrs' => '2006''mth' => '1''day' => '1' ),
            
    // only specify the requires where the regex is different from the default regex
            
    array( 'controller' => '[a-z]+''action' => '[a-z]+''yrs' => '[0-9]{4}''mth' => '[0-9]{2}''day' => '[0-9]{2}' ) );
        
        
    // controller, action and an id only
        
    $router -> addRoute':controller/:action/id/:id'
            array( 
    'controller' => 'index''action' => 'index''id' => '' ),
            array( 
    'controller' => '[a-z]+''action' => '[a-z]+''id' => '[0-9]+' ) );
        
        
    // default route
        // controller and action only
        
    $router -> addRoute':controller/:action'
            array( 
    'controller' => 'controller_index''action' => 'action_index' ), 
            array( 
    'controller' => '[a-z]+''action' => '[a-z]+' ) );
        
    $rs $router -> route(); 
    Hope this is of some use anyways, it's helped me to understand it some more anyways.

  13. #13
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Dr Livingston
    Following original script taken from SVN proposals of Zend Framework; Matt, does this give you a better idea of routes?
    Nice. Thanks for that I should have looked at that earlier. That's the kind of thing I want to add. I think there is a need though for a pre-setup default style of routing. It'd be a drag to set up routes for every application/request. That was my goal for the code I originally posted.

    Also, thanks for the solution to the recursive strip_slashes problem!

    Any other comments on the code I posted?

    -matt

  14. #14
    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 mwmitchell
    Nice. Thanks for that I should have looked at that earlier. That's the kind of thing I want to add. I think there is a need though for a pre-setup default style of routing. It'd be a drag to set up routes for every application/request.
    -matt
    Thats what APC/any-other-mem-caching-extension is for.

  15. #15
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    Thats what APC/any-other-mem-caching-extension is for.
    Sorry, I'm not sure what you mean! Can you explain?

  16. #16
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    One problem is the _removeSlashes() function is recursive. Making it possible to crash the server by

    http://localhost/shop/cart/add/id/43?a=[][][][][] ..lots more [].. [][][][][]

  17. #17
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    One problem is the _removeSlashes() function is recursive. Making it possible to crash the server by

    http://localhost/shop/cart/add/id/43?a=[][][][][] ..lots more [].. [][][][][]
    There's a limit to the size of a querystring, so I would guess that it isn't a problem. Did you try it ?
    Either way, that should cause a stack overflow, which just crashes the running pocess of PHP. I don't think it would cause any leaks or take down apache ?

  18. #18
    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 kyberfabrikken
    There's a limit to the size of a querystring, so I would guess that it isn't a problem. Did you try it ?
    Either way, that should cause a stack overflow, which just crashes the running pocess of PHP. I don't think it would cause any leaks or take down apache ?
    It does crash. Im not actually sure there is a limit, outside of the browsers (4k for mozilla, and something smaller for IE). but

    file_get_contents('http://localhost/index.php?a='.str_repeat('[]', 10000); will crash things

    One method, taken from php|architect’s Guide to PHP Security by Ilia Alshanetsky, is to use iteration instead.
    PHP Code:
    if (get_magic_quotes_gpc()) 
    {
         
    $input = array(&$_GET, &$_POST, &$_COOKIE, &$_ENV, &$_SERVER);
         while (list(
    $k,$v) = each($input))
         {
             foreach (
    $v as $key => $val
             {
                 if (!
    is_array($val)) 
                 {
                     
    $input[$k][$key] = stripslashes($val);
                     continue;
                 }
                 
    $input[] =& $input[$k][$key];
             }
         }
         unset(
    $input);


  19. #19
    eschew sesquipedalians silver trophy sweatje's Avatar
    Join Date
    Jun 2003
    Location
    Iowa, USA
    Posts
    3,749
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    There's a limit to the size of a querystring, so I would guess that it isn't a problem.
    Per RFC 2616 Section 3.2.1 there is no defined limit in the specification, you are therefore only dealing with implementation (both server and client) limitations.

    Quote Originally Posted by RFC
    RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1

    3.2.1 General Syntax

    URIs in HTTP can be represented in absolute form or relative to some
    known base URI [11], depending upon the context of their use. The two
    forms are differentiated by the fact that absolute URIs always begin
    with a scheme name followed by a colon. For definitive information on
    URL syntax and semantics, see "Uniform Resource Identifiers (URI):
    Generic Syntax and Semantics," RFC 2396 [42] (which replaces RFCs
    1738 [4] and RFC 1808 [11]). This specification adopts the
    definitions of "URI-reference", "absoluteURI", "relativeURI", "port",
    "host","abs_path", "rel_path", and "authority" from that
    specification.

    The HTTP protocol does not place any a priori limit on the length of
    a URI. Servers MUST be able to handle the URI of any resource they
    serve, and SHOULD be able to handle URIs of unbounded length if they
    provide GET-based forms that could generate such URIs. A server
    SHOULD return 414 (Request-URI Too Long) status if a URI is longer
    than the server can handle (see section 10.4.15).

    Note: Servers ought to be cautious about depending on URI lengths
    above 255 bytes, because some older client or proxy
    implementations might not properly support these lengths.
    Jason Sweat ZCE - jsweat_php@yahoo.com
    Book: PHP Patterns
    Good Stuff: SimpleTest PHPUnit FireFox ADOdb YUI
    Detestable (adjective): software that isn't testable.

  20. #20
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > I think there is a need though for a pre-setup default style of routing.



    You could have a Factory of some sort to generate the required routes for you, ie

    PHP Code:
    // index.php
    Map::getInstance() -> load'path/to/xml/file.xml' ); 
    This would then fetch the routes to use and preload them, so you could use something like this for example,

    PHP Code:
    if( Map::getInstance() -> match$path ) ) {
    // found a suitable route...
    }
    // ... 
    The Map::match( $path ); class method would iterate over the routes that were previously loaded, and return the appropriate one for you

  21. #21
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    > Any other comments on the code I posted?

    I had a brief look but not had the time to delve too much into it unfortunately... Any luck I'll get a chance to look at them properly soon

  22. #22
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    One thing I'm not clear on is how "stable" indexed parameters are compared to paired params? Example:

    /myapp/listview/per-page/4/sort-by/name

    Now, with indexed params:

    /myapp/listview/4/name

    Short and sweet... But what happens if the url changes: no per-page value? As in:

    /myapp/listview/name

    Doesn't "name" become the new per-page value? With paried params, this works fine:

    /myapp/listview/sort-by/name

    How is this dealt with? It seems like it's MORE work to validate the value type of each param than just using pairs. I know it's good practice to validate, but do you see what I'm getting at?

    -matt

  23. #23
    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 mwmitchell
    One thing I'm not clear on is how "stable" indexed parameters are compared to paired params? Example:

    /myapp/listview/per-page/4/sort-by/name

    Now, with indexed params:

    /myapp/listview/4/name

    Short and sweet... But what happens if the url changes: no per-page value? As in:

    /myapp/listview/name

    Doesn't "name" become the new per-page value? With paried params, this works fine:

    /myapp/listview/sort-by/name

    How is this dealt with? It seems like it's MORE work to validate the value type of each param than just using pairs. I know it's good practice to validate, but do you see what I'm getting at?

    -matt
    IMHO, I see no advantage of using paired params in the path, over using the query string. So /myapp/listview?sort-by=name&per-page=4.

    Perhaps using the defaults, sort-by=name and per-page=4 this would reduce to /myapp/listview.

  24. #24
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    IMHO, I see no advantage of using paired params in the path, over using the query string. So /myapp/listview?sort-by=name&per-page=4.

    Perhaps using the defaults, sort-by=name and per-page=4 this would reduce to /myapp/listview.
    I totally agree. It's abuse of the URI-concept. Using slashes rather than ampersands doesn't magically turn your querystring into a URI. There may be an esthetic reasoning behind it, but I don't think that qualifies as an argument.

    The common $module/$action URI-mapping is also questionable. The location-part of a URI should designate the location of a resource, but in this case, only the first part does this location, while the last part is a command on that resource. Ideally the command should be expressed through the HTTP-method. Since this is not not always practical, it must be propagated over either the location or the querystring (or headers, but this isn't practical either) - I do think that it would have been more correct to use the querystring, than the location.

  25. #25
    SitePoint Guru
    Join Date
    May 2003
    Location
    virginia
    Posts
    988
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken
    I totally agree. It's abuse of the URI-concept. Using slashes rather than ampersands doesn't magically turn your querystring into a URI. There may be an esthetic reasoning behind it, but I don't think that qualifies as an argument.
    OK, so in your opinion... does it seem more correct to use routing (not paired params) for parameters and a strict "real" location path for the controller and action? Something like:

    /products/view/54

    I realize that this isn't RESTful, but I can't see the point in implementing that REST because we don't have DELETE or PUT? I can see how you'd want to come as close as possible though. In which, the url above would be:

    /products/54

    Is that correct?

    It seems so much more managable (for a programmer?) if you don't map/route your controllers, and let the filesystem provide the mapping. I do like the routing of extra parameters though, in comparison to paired params.


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
  •