SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 40
  1. #1
    SitePoint Evangelist
    Join Date
    Jun 2008
    Posts
    455
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Controller Routing

    Hi guys.

    Are there any good examples of controller routing/mapping I could take a look at?

    I'm trying to write my own, but at the moment its a bit too strict as in you have to define the entire url and then explicitly set the routing parameters.

    What I want to be able to do is have wildcards rather than having to define every possible solution/outcome in the router config.

    Cheers.

  2. #2
    Passionate Web Developer Egyptechno's Avatar
    Join Date
    Jan 2004
    Location
    Dubai
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    if this helps :

    Code:
    $do = $_GET['do'];
    $do = mysql_escape_string($do);
    
    if (file_exists($do.'/index.php'){
    include($do.'/run.php');
    }
    something like that .. you should b having " modules " folder for say, and each categorie should b in seperatd folder contained inside wiz index and run .php
    I adore PHP

  3. #3
    Utopia, Inc. silver trophy
    ScallioXTX's Avatar
    Join Date
    Aug 2008
    Location
    The Netherlands
    Posts
    8,897
    Mentioned
    138 Post(s)
    Tagged
    2 Thread(s)
    Off Topic:


    @Egyptechno: Why would you use mysql_escape_string() for something you're not sending to the database?
    And how about checking paths such that I can't provide $_GET['do'] as '../../some/path/i-m/not/supposed/to/access' ?
    Rémon - Hosting Advisor

    Minimal Bookmarks Tree
    My Google Chrome extension: browsing bookmarks made easy

  4. #4
    Passionate Web Developer Egyptechno's Avatar
    Join Date
    Jan 2004
    Location
    Dubai
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    @scallioXTX : well i provided previous code supposing that $_GET['do'] will be only 'access' and mysql_escape_string .. besides it doesn't only work with db .. it's useful to escape strings and check for slashes or quotes .. you think it's not needed?
    I adore PHP

  5. #5
    ¬.¬ shoooo... silver trophy logic_earth's Avatar
    Join Date
    Oct 2005
    Location
    CA
    Posts
    9,013
    Mentioned
    8 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Egyptechno View Post
    ...mysql_escape_string...it's useful to escape strings and check for slashes or quotes .. you think it's not needed?
    You are using the wrong method then. mysql_escape if for using with SQL queries. Not general escaping, in any case escaping is not something you should be doing here. You should validate and filter. Accepting paths from unknown sources is the worst thing you can do on a web application.
    Logic without the fatal effects.
    All code snippets are licensed under WTFPL.


  6. #6
    SitePoint Evangelist
    Join Date
    Jun 2008
    Posts
    455
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for the example however all the example does is include a file. It does not route a request.

    I'll download a few PHP Frameworks and see what I can come up with.

    Logicearth is correct in saying that mysql_escape_string is being used incorrectly. The name of the functions kind of gives away the purpose of the function.

    Thanks for the help anyway.

  7. #7
    Passionate Web Developer Egyptechno's Avatar
    Join Date
    Jan 2004
    Location
    Dubai
    Posts
    259
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    well .. i can agree escaping is not the best practice here .. i need to validated paths, but i belive that mysql_escape_string is generally for escaping strings .. no need to b used only wiz mysql queries !


    wish you luck @L4DD13
    I adore PHP

  8. #8
    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)
    Most routers are based on some kind of regular expression dialect. For example Zend Framework s Router. The inspiration comes a lot from Ruby on Rails. If you're looking for inspiration, reading the documentation for Rails may be useful for you too.

    As an alternative to specifying the routes at a central point, you can delegate it to a hierarchy of controllers. This is a core concept of Konstrukt.

  9. #9
    ¬.¬ shoooo... silver trophy logic_earth's Avatar
    Join Date
    Oct 2005
    Location
    CA
    Posts
    9,013
    Mentioned
    8 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Egyptechno View Post
    ...but i belive that mysql_escape_string is generally for escaping strings .. no need to b used only wiz mysql queries !
    It is not for general escaping. It is a broken implementation for escaping strings for SQL to be sent to MySQL. And it is broken for that as well. What you "belive" is wrong. That is all I'm going to say further on this as it is off topic.
    Logic without the fatal effects.
    All code snippets are licensed under WTFPL.


  10. #10
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    For routing there are pretty much 2 options:

    1) A routing table where each route is defined with the components it will initiate.

    2) A convention over configuration approach where routing is based on the filesystem and/or whether classes/methods exist.


    Both have their advantages but I prefer the latter as it means far, far less set up.


    For example,

    /admin/user/edit/12

    What could happen here is the admin controller would initiate the user controller and call the action "edit" with the first parameter as 12. All this would need a fairly complex routing table and script to deal with it.

    If you're doing guesswork, it's easy for the application to apply these rules based on what files/classes/actions exist. E.g. if the controller "Admin" existed but there was no subcontroller called "user" it could call admin's default action with 3 parameters.

  11. #11
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by L4DD13 View Post
    What I want to be able to do is have wildcards rather than having to define every possible solution/outcome in the router config.Cheers.
    Hello Michael,

    basically you can choose regexps or progressive parsing.

    Regexps are fine but their scalabity is IMHO worse. Having a route
    Code:
    /:component/:categories/:actions
    matched e.g. by URLs
    Code:
    /articles/USA/latest
    /articles/astronomy/latest
    /events/music/show
    means that you probably have to compose more regexps, because each component could have different categories and actions.

    Progressive scanning and matching (part by part) is probably better, but I did not do any test on this subject.

  12. #12
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    The URL Router in Alloy Framework (currently a work-in-progress) might be of some use to you. You can use Alloy\Router and Alloy\Router\Route independent of the framework itself, because there is no coupling or outside dependencies.

    Basically, It uses a routing syntax like this:
    PHP Code:
    // View vehicle record
    $router->route('vehicle''/<#year>/<:make>/<:model>')
        ->
    defaults(array(
            
    'module' => 'Vehicle',
            
    'action' => 'view',
            
    'format' => 'html'
        
    )); 
    And does a string match on a URL to produce an array of key/value pairs like this:
    PHP Code:
    // URL like this
    $url $_SERVER['REQUEST_URI']; // '2008/ferrari/f430'

    // Match URL to provided routes
    $params $router->match('GET'$url); // <HTTP Method, URL>

    /*
    // Resulting params would look like this:
    $params = array(
        'year' => '2008',
        'make' => 'ferrari',
        'model' => 'f430',
        'module' => 'Vehicle',
        'action' => 'view',
        'format' => 'html'
    );
    */ 
    It also does reverse matching to produce URLs for links:
    PHP Code:
    // To produce '2008/ferrari/f430' again
    $linkUrl $router->url('vehicle', array(
        
    'year' => '2008',
        
    'make' => 'ferrari',
        
    'model' => 'f430'
    )); 
    It's the only PHP URL router I know of that is this easy and flexible, is REST-based, and has no other framework inter-dependencies. It was modeled after the URL router in Merb and Rails.

  13. #13
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    I think routing tables like that are messy. I prefer a "best match" approach similar to what Mastodont was implying

    e.g.

    /admin/users/edit/12

    explode on /

    does module "admin" exist?
    no - call $defaultModule / $defaultController / $defaultAction with parameters "admin", "users", "edit", "12"
    yes - set admin module then:

    does controller "admin/users" exist?
    no - call admin module / $defaultController / $defaultAction with parameters "users", "edit", "12"
    yes - initiate admin/users controller then:

    does method "edit" exist on the users controller?
    no - call admin module / users controller / $defaultAction with parameters "edit", "12"
    yes - call admin module / users controller / "edit" action with parameters "12"


    Add and replace levels as necessary by the application but this approach is better because once the router is set up it requires zero configuration yet you can specify defaults where required or create a very minimal routing table if you need to do something drastically different.

  14. #14
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    I think routing tables like that are messy. I prefer a "best match" approach similar to what Mastodont was implying

    e.g.

    /admin/users/edit/12

    explode on /
    The trouble with that approach is that it's terribly inflexible. You're essentially hard-wiring the URL to a pre-determined set structure. URLs are meant to represent a browse-able hierarchy of resources that cascade down, i.e:

    http://autoridge.com/2008/ferrari/f430
    http://autoridge.com/2008/ferrari
    http://autoridge.com/2008

    This type of structure would be impossible with a hard-wired "explode" approach, or else would require splitting up functionality into specifically named modules/controllers for it to work - not even counting the fact that PHP won't let you start variable, function, or class names with numbers, so the "2008" portion might very well be impossible, depending on the framework's naming conventions.

    As for the 'admin' prefix, it could be either included in the route as an optional parameter, or detected before being parsed by the router, and added as a special flag to the dispatcher. There are many possible ways to achieve this functionality.

  15. #15
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Not really, in that case it would be caught as parameters to the default action on the default controller, e.g.

    PHP Code:
    class MyDefaultController extends Controller {
        public 
    $defaultAction 'main';

        public function 
    main($year null$make null$model null) {
            
    //...    
        
    }


    the default controller would be defined in the general project configuration.

    If it really is an edge case where that doesn't work (e.g. it may be putting too much work in the controller) then there should still be room to override it. However, I've found the best match approach works in 95% of cases.


    Edit: How would you handle categorisation where you don't know what level the end point is?

    e.g. On a site I deal with we have categories e.g.

    e.g.
    /accessories/bags/alpinestars/
    which has a product e.g.
    /accessories/bags/alpinestars/alpinestars_ripper_backpack
    or
    /gloves/ducati/ducati_corse_gloves

    The product (end point) could be N levels down the chain depending how it's been categorised and you don't know whether any given URI is a category or a product

  16. #16
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,011
    Mentioned
    56 Post(s)
    Tagged
    0 Thread(s)
    I was working on this earlier today. I'll go over the psuedo code of what I'm trying.

    Routing requests is the responsibility of the framework's core in my setup. The system is event based, the event is encoded as the "php file" at the end of the url. "index.php" is the default event (any extension is allowed, but the presence of the extension marks the member as a event).

    The path is exploded on / and then each element is checked against the tree. If the path goes down a nonexistent branch then the last found path entry is used. The controller responsible for that path is started and it makes the decision on what to do with the extra path, though the default action is to throw 404.

    An examples might help. Consider the path /articles/November/23/richard's_column.html

    We have a controller named articles so core will find it second (it finds "home" first), but the core can't find anything for the rest of the path, so it starts articles and tells it the remaining path is /November/23 and the event is "richard's_column.html"

    The articles Page controller knows what to do from there.

    The default handling though is to check the events table, see if the event is allowed on that page (all pages must have an index event though since it is default). Next it checks to see if the event is requested properly - all events are get or post. It is however possible in the framework to have two events with the same name but different request methods - for example the GET edit method returns the form to be entered out, the POST edit method actually saves the changes to the database. Next it determines if the event is javascript initiated and chooses the responder object based on that fact. There are a few methods exclusive to js or html but I try to keep these to a minimum and only where they make sense. For example, the method POST blurValidate sends a field name and its current value to PHP and PHP returns boolean true or false if it passes server side validation. The php response is a javascript object, and there's little to no point in allowing it to be accessed without js except in debug mode when testing.

    The database holds the pages and lets the user choose the setup pretty much however they want. When I'm finished pages won't have to have a class defined for them, they can instead inherit this from their parent. Same with blocks and templates. So in the above the articles page can inherit some or all of the blocks off the home page.

  17. #17
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    On a somewhat interesting and related note, it's not always possible to route everything. A good edge case is dealing with a searchable list.

    Say I have a list (of products/orders/users, anything really) with a sort option and a couple of text filters on various fields.

    You could do /user/list/[filter1]/[filter2]/[sortoption] (e.g. /user/list/tom/2010-01-01/1 ) but this requires the user to supply something for each field (or it might be a checkbox which if unchecked wont have a value so you'd end up with/user/list///1 which is undesirable). Imho, it's better to revert to GET. /user/list/?filter1=tom&filter2=2010-01-01&sort=1 the other alternative is to mimic it (e.g. /user/list/filter1=tom;filter2=;sort=1 but then you cant use simple GET forms.

  18. #18
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,011
    Mentioned
    56 Post(s)
    Tagged
    0 Thread(s)
    Agreed, but the router still must reach the search demon. Also there's no reason for some of the data to be split as long as the issue isn't being forced.

    Using your product search example the user might start a search from a page that is two categories down from root - say, computer accessories, input devices. So their current URL is

    /accessories/input_devices/index.html

    The router's information about those categories is relevant to the search, especially if the widget gives the user a checkbox on whether to search this section or the whole site. Hence the search URL should still be

    /accessories/input_devices/index.html?param=1&foo=3&bar=mice

    rather than

    search.php?group=accessories&category=input_devices&param=1...

    In this case I would use javascript to rewrite the form action depending on the checked status of the search "this section" or "whole site" so that if the user searches the whole site the action is appropriate to that for bookmarking. If js is off though the search will still resolve, but php should to send a 300 redirect to make sure the browser bookmarks the results page as a sitewide search page and not a section page.

  19. #19
    PHP/Rails Developer Czaries's Avatar
    Join Date
    May 2004
    Location
    Central USA
    Posts
    806
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by TomB View Post
    Not really, in that case it would be caught as parameters to the default action on the default controller, e.g.

    PHP Code:
    class MyDefaultController extends Controller {
        public 
    $defaultAction 'main';

        public function 
    main($year null$make null$model null) {
            
    //...    
        
    }


    I think that's a terrible solution. You are again forcing a specifically named controller to handle a specific request when it should just be handled by it's own component.

    Quote Originally Posted by TomB View Post
    Edit: How would you handle categorisation where you don't know what level the end point is?

    e.g. On a site I deal with we have categories e.g.

    e.g.
    /accessories/bags/alpinestars/
    which has a product e.g.
    /accessories/bags/alpinestars/alpinestars_ripper_backpack
    or
    /gloves/ducati/ducati_corse_gloves

    The product (end point) could be N levels down the chain depending how it's been categorised and you don't know whether any given URI is a category or a product
    You would handle this with wildcard parameters or your own custom regex rule:

    PHP Code:
    // <*param> = Wildcard capture (.*) - matches anything, including directory separators
    $router->route('product_page''/<*category_path>/<:product>')
        ->
    defaults(array('module' => 'products''action' => 'view')); 
    You would then get two params to pass onto your product controller's "view" action - 'category_path', and 'product'. The product controller could then explode the category_path or do whatever else it needs to do with it, and lookup the product by alias. This same logic applies to your search filter example. Wildcard parameters are very useful for these kinds of edge cases.

  20. #20
    SitePoint Addict Mastodont's Avatar
    Join Date
    Mar 2007
    Location
    Czech Republic
    Posts
    375
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Czaries View Post
    http://autoridge.com/2008/ferrari/f430
    http://autoridge.com/2008/ferrari
    http://autoridge.com/2008

    This type of structure would be impossible with a hard-wired "explode" approach, or else would require splitting up functionality into specifically named modules/controllers for it to work - not even counting the fact that PHP won't let you start variable, function, or class names with numbers, so the "2008" portion might very well be impossible, depending on the framework's naming conventions.
    These URLs would be matched by routes
    Code:
    /:year/:producer/:car
    /:year/:producer
    /:year
    Router should return controller Cars with these parameters. I see no issue here.

  21. #21
    SitePoint Wizard bronze trophy
    Join Date
    Jul 2006
    Location
    Augusta, Georgia, United States
    Posts
    4,048
    Mentioned
    16 Post(s)
    Tagged
    3 Thread(s)
    I prefer pattern mapping over binding the request to the controller by some naming convention. Delegating routing via patterns or callbacks is much more flexible and decouples the routing and controller logic. It is a little more complex, but leaves a lot of room to modify urls as desired by a client and easily map "friendly urls" to their "non-friendly" counter part. Czaries example is a pretty good that allows for flexibility in terms of dynamic and static routing.

  22. #22
    SitePoint Guru bronze trophy TomB's Avatar
    Join Date
    Oct 2005
    Location
    Milton Keynes, UK
    Posts
    988
    Mentioned
    9 Post(s)
    Tagged
    2 Thread(s)
    Quote Originally Posted by Czaries View Post
    I think that's a terrible solution. You are again forcing a specifically named controller to handle a specific request when it should just be handled by it's own component.



    You would handle this with wildcard parameters or your own custom regex rule:

    PHP Code:
    // <*param> = Wildcard capture (.*) - matches anything, including directory separators
    $router->route('product_page''/<*category_path>/<:product>')
        ->
    defaults(array('module' => 'products''action' => 'view')); 
    You would then get two params to pass onto your product controller's "view" action - 'category_path', and 'product'. The product controller could then explode the category_path or do whatever else it needs to do with it, and lookup the product by alias. This same logic applies to your search filter example. Wildcard parameters are very useful for these kinds of edge cases.
    But the URL could end with either a category or a product. You have no way to know which it is.

    Using an ending / as a notifier is bad. You wouldn't want:

    /accessories/bags/alpinestars

    to initiate the product controller and have

    /accessories/bags/alpinestars/

    initiate the category controller as they're the same page (and the first would cause a "product not found" error). There's also usability issues presented by forcing a trailing slash as a required element. One should forward to the other for SEO but having /foo/bar different to /foo/bar/ is bad.

  23. #23
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,011
    Mentioned
    56 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by oddz View Post
    I prefer pattern mapping over binding the request to the controller by some naming convention. Delegating routing via patterns or callbacks is much more flexible and decouples the routing and controller logic. It is a little more complex, but leaves a lot of room to modify urls as desired by a client and easily map "friendly urls" to their "non-friendly" counter part. Czaries example is a pretty good that allows for flexibility in terms of dynamic and static routing.
    static routing??

    If you're routing static pages, you're doing something wrong.

  24. #24
    SitePoint Wizard silver trophybronze trophy Stormrider's Avatar
    Join Date
    Sep 2006
    Location
    Nottingham, UK
    Posts
    3,133
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)
    In my framework, I have a routes.xml file which defines certain routes based on patterns, eg {string} or {int}. The entry would then define the Controller and Action, and any parameters to be passed to them. I also make it responsible for redirects (which is an option in the routes.xml file) and also switching to a secure server or not, so these are handled pretty early on in the lifecycle of the request.

    Example file:

    Code:
    <routes>
      <index>
        <execute controller="HomeController" action="index" secure="no" />
      </index>
    
      <notfound>
        <execute controller="ErrorController" action="Error404" />
      </notfound>
    
      <route match="test1">
        <execute controller="TestController" />
      </route>
    
     <route match="test2">
      <execute controller="TestController" action="TestAction" />
     </route>
    
     <route match="test3">
      <execute controller="TestController" action="TestAction2" secure="yes" />
    
      <param>Chris Emerson</param>
     </route>
    
     <route match="test3/{string}">
      <execute controller="TestController" action="TestAction2" />
    
      <param>&#37;1</param>
     </route>
    
     <route match="test4">
      <redirect route="test1" secure="no" />
     </route>
    
     <route match="test5">
      <redirect uri="http://www.example.com" />
     </route>
    </routes>
    If no action is defined, index is used as default.

    It's currently limited a bit, in that 'excess' url fragments aren't catered for, but I can easily add this I think with another token - {url} or something - or another attribute of the route which will pass the rest of the url on to the controller/action.

    The dispatcher is called with the url string by index php, it parses this file, finds what it should do and loads the controller from there, or redirects, or whatever it needs to do.

    It's not perfect, but then my whole framework is a work in progress!

  25. #25
    I solve practical problems. bronze trophy
    Michael Morris's Avatar
    Join Date
    Jan 2008
    Location
    Knoxville TN
    Posts
    2,011
    Mentioned
    56 Post(s)
    Tagged
    0 Thread(s)
    Here's my router described earlier.

    PHP Code:
    public static function parseRequest() {

            
    $path array_filter(explode('/'$_SERVER['REDIRECT_URL']));
            
    $pages Core::$pages;
            
            if ( 
    pathinfo($_SERVER['REDIRECT_URL'], PATHINFO_EXTENSION )) {
                
    $event pathinfo($_SERVER['REDIRECT_URL'], PATHINFO_FILENAME );
                
    array_pop($path);
            } else {
                
    $event 'index';
            }
            
            
    $path array_reverse($path);
            
    $path[] = '';
            
    $found = array();

            do  {
                
    $p array_pop($path);

                foreach (
    $pages as $page) {
                    if (
    $page['attributes']['name'] == $p) {
                        
    $found $page;
                        
    $found['pathAfter'] = $path;
                        break;
                    }
                }
                
                if (isset(
    $found['children'])) {
                    
    $pages $found['children'];
                }
            
            } while (
    count($path) > 0);
            
            if (empty(
    $found)) {
                die(
    'No Homepage Defined.');
            }
            
            
    $class $found['attributes']['class'];
            
            
    Core::$page = new $class$found );
            
    Core::$page->parseEvent$event );
        } 
    Eventually the die statement will become a proper exception object, but I'm in the middle of a rewrite and I want things to die hard when they go wrong. The array_filter call at the top addresses the issue of url crap like http://www.site.com///i//don't//really/pay/much///attention//to/slash/placement////

    All the empty elements are pulled by the filter. After reversing the array I do put one back, but that for is the home page controller which is the only page in the system with no name.

    Core::$pages is pulled from the database in development, or from the master cache file in production. The function above is part of the Core class.

    It's simple and works well enough. Any additional fidgeting with the page can be done by the real controller, the Core method's only job is to accurately find that file.
    Last edited by ScallioXTX; Oct 5, 2010 at 06:59. Reason: De-linkified example link


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
  •