SitePoint Sponsor

User Tag List

Page 1 of 2 12 LastLast
Results 1 to 25 of 46
  1. #1
    SitePoint Enthusiast
    Join Date
    Mar 2003
    Location
    Fredericton NB
    Posts
    79
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    data validation and application architecture

    I'm in the planning stage for my next-generation of application architecture, and I'm a bit unsure of how I want to handle data validation.

    In my current applications, I have one class for each database table. The data is not validated within the class itself. Instead, the script that creates the object instantiates a form validator object. If all of the data is valid, the object is saved to the database - if not, the user is presented with errors.

    This obviously has some disadvantages, the biggest one being that if you are calling this class from multiple scripts, there is no way to globally modify the data validators.

    So, I think that I should incorporate data validation in the class itself. However, this has some pitfalls too. I can think of plenty of examples where I would like to ignore the validator for specific cases, so more than likely, I would need to also add some way of overriding the validation rules if needed.

    Any thoughts on this subject? Any other disadvantages that I might be overlooking?
    Red Cow Technologies, Inc
    RealAdmin - Software For Real Estate Agents
    myBusinessAdmin - Affordable CMS and web design solution.

  2. #2
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Off Topic:

    Data validation in general is the hardest thing to do in an application. It's the thing that always gets me in the development stage. And still you'll always have to do it, and preferably do it well. The best approach? Damned if I knew.

  3. #3
    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)
    You should at least distinguish between two types of validation ; You'll want one type of validation in your modellayer, and another one in the controller layer. Either way, validation is a rather complicated issue, and I think it's better to understand it as an aspect of your application, rather than a distinct layer or component. You may be able to automate some of the routinely rules with a library/framework, but you'll always need specialized rules here and there, so don't overdo the generics. What's more important is how you handle situations where the rules are broken (eg. exceptions/errors).

  4. #4
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    and another one in the controller layer.
    I would agree, but in saying that, you would want to encapsulate the validation, and validate it before you reach the Controller; Do you want the Controller in question to have to deal with the invalid data?

    As has already been said though, it's a complex issue, and there isn't any quick fix

  5. #5
    SitePoint Enthusiast
    Join Date
    Dec 2005
    Posts
    53
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well you have to deal with the invalid data at some point, ideally before you do any major processing on the data. And you have to deal with it in a place where you understand the meaning of the data, well thats how I look at it anyway. Ray Oliver, what you are proposing is basically how I perform validation. I am interested in others opinions of this approach. I don't really understand the problem you are pointing out with this approach. Simply have a method in the class which performs the validation, if it fails don't persist the data. Make his method flexible enough so it can perform validation on a subset of the fields or even none at all.

    Basically I see validation as a part of the business logic, the class(es) which retrieves / stores data from the database knows about the meaning of the data and so performs the validation. Well in my setup the class actually uses a utility class which does the grunt work but the validation rules are defined in the active record class(I think thats the pattern).

    kyberfabrikken and Dr Livingston, I am interested in what type of validation you are talking about in/before the controller? I would like to perform validation before the controller but really I can't work out how. I don't want to be performing regular expressions on all inputs at this point because the way I see it that is business logic.

    The way I kind of avoid the problem is by not performing any serious processing on the data untill it has been validated by the model layer. Often one of the first thing my controllers do is get a model to validate some data. This way in my architecture is perfectly secure, tainted data is only dangerous when you apply processing to it, and I always validate before serious processing.

    However its probably not the cleanest way, perhaps there is a better way to do this?

  6. #6
    SitePoint Addict
    Join Date
    Mar 2005
    Posts
    251
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Hello.

    Validation basically happens naturally at all three layers. I'll explain by way of an example.

    At the view layer, the user enters information into a form which is validated(javascript). Whilst the application does not rely on such validation it is still, in most cases, essential to provide it.

    Back at the database, the model also has some validation requirements. You can't for instance have text content inserted into an int field of a database. Equally you can't duplicate a field that has been setup as unique etc. So before the model inserts into the database it would be correct to make sure that the data is fit for the database.

    However data can be fit for the database, but completely unfit for the application. For instance I could choose a completely valid date to start my holiday booking, unfortunately that date could be January 1st 1985, which is extremely invalid as far as my application goes.

    The separation of application into these three layers thus requires three layers of validation. If your model layer is checking that a date is in the future, it is imposing on the business logic layer, creating a dependency. Conversely if the controller enforces that the data it has is suitable for entry into the database. Then the controller has to be responsible or dependent on the database or model layer.

    So it's fairly straightforward to do model validation. You have access to the database structure and before the model performs an update/insert it does a simple check against the DB structure. Therefore no unfit data can get into the database, but there is no dependency.

    Hopefully the model layer will never catch invalid data... if it does it would probably be good to throw an application error as it means that your controller has let through something it shouldn't.

    So the short summary is that important validation is best done in the controller layer. But the other two layers also must validate as well. What is thus needed is a strategy to deal with this, but I'll let others comment on this.

  7. #7
    SitePoint Enthusiast boroda's Avatar
    Join Date
    Jan 2006
    Location
    Moscow, Russia
    Posts
    61
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    As I can understood the patterns right, it is usefull to separate model data and the view data. So we can have incorrect data in the view layer (just to render filled form with the error message), but we can not have incorrect data in the model layer.

  8. #8
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I am interested in what type of validation you are talking about in/before the controller?
    If you submit a form, which has x number of form elements, you would want to be sure that you have those elements, ie Keys in $_POST for example?

    If there is a missing key, then of course, you can't proceed to execute the Controller in question, so you'd want to validate $_POST prior to reaching a Controller.

    If you are talking about validation of actual data, as in a form submission, I suppose that is business data, then it's not here nor there if you leave the validation to either the Controller, or a Model I suppose?

  9. #9
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Dr Livingston
    If you submit a form, which has x number of form elements, you would want to be sure that you have those elements, ie Keys in $_POST for example?
    Yes. Edit: No, not really the way you put it. I'm not getting your point in separating input validation (?) from data validation.
    If there is a missing key, then of course, you can't proceed to execute the Controller in question, so you'd want to validate $_POST prior to reaching a Controller.
    Not true -- if you check for the elements in the Controller. Does anyone know how exactly Rails does this? I haven't looked into it, but it could prove out to be a valuable comparison.

  10. #10
    SitePoint Enthusiast
    Join Date
    Dec 2005
    Posts
    53
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    rossriley: Interesting, I always thought that the controller/view provided the user interface and all business logic belonged in the model layer(MVC pattern). The way I see it is that all business logic is tied in with the data so you may as well put all business logic in the model layer. So you think it is fine to have business logic inside the controller? Definitions I have read of MVC as applied to website dev clearly state that the controller/view form the user interface and all core business/domain logic belongs in the model layer.

    You have a good point about javascript validation on the client. So yes I guess validation must be performed across all layers of an application.

    Quote Originally Posted by Dr Livingston
    If you are talking about validation of actual data, as in a form submission, I suppose that is business data, then it's not here nor there if you leave the validation to either the Controller, or a Model I suppose?
    I always thought that business logic should not be in the controller, the controller forms a part of the user interface.
    Last edited by Hunch; Feb 12, 2006 at 05:43.

  11. #11
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    ...

    I tend to validate data from a form submission for example, in the Controller, ie Using a number of rules per form element, but then as stated earlier, you need to verify the data is also fit for the database, and this type of validation belongs in the Model

    However there are some people who validate form submitted data in the Model as well, I tend to think that's a personal preference, thus I put forward,

    then it's not here nor there if you leave the validation to either the Controller, or a Model I suppose?
    If you want to validate form submitted data in the Controller, or in a Model, then it's upto yourself really?

  12. #12
    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 Ezku
    Yes. Edit: No, not really the way you put it. I'm not getting your point in separating input validation (?) from data validation.
    Not true -- if you check for the elements in the Controller. Does anyone know how exactly Rails does this? I haven't looked into it, but it could prove out to be a valuable comparison.
    The initial validation is done on the url path, I believe,

    Code:
    map.connect 'date/:year/:month/:day', :controller => 'blog', :action => 'by_date',
                  :month => nil, :day => nil,
                  :requirements => {:year => /\d{4}/, :day => /\d{1,2}/, :month => /\d{1,2}/}
    So if any requirement isn't statisfied then the route isn't going to get called, which is trivial todo in PHP

    PHP Code:
            if (preg_match('#^/?date/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})\?$#'$path$v))
            {
                  
    var_dump($v);              
            } 

  13. #13
    SitePoint Enthusiast
    Join Date
    Dec 2005
    Posts
    53
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Well in the end its always up to yourself

    My concern with putting it in the controller is that I try and keep all business logic away from the controller. But even this approach might be misguided so I am interested in what others think.

    A good thing about having all validation in a model is that your validation is guaranteed to remain consistent across your website. So if you have a form which say creates a new product and another form with edits a product, both these forms will use the same model class and need to validate the user submitted data. By reusing the model class across both forms you ensure the validation rules are implemented consistently. If you have them in seperate controllers there is no such guarantee. On a large website with heap of forms it would be very easy to implement validation rules inconsistently, especially if they change at some point. Well easy for someone as lazy as me anyway

  14. #14
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    which is trivial todo in PHP
    Compared to what? ... Ruby?

  15. #15
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    So if any requirement isn't statisfied then the route isn't going to get called, which is trivial todo in PHP
    PHP Code:
    if (preg_match('#^/?date/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})\?$#'$path$v))
            {     
    var_dump($v);        } 
    Trivial? Why am I not understanding a single thing in that regexp?

  16. #16
    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 Dr Livingston
    Compared to what? ... Ruby?
    Well suppose it depends on regexp comfort levels. For complete Ruby style..

    PHP Code:
    class Route
    {
        protected 
    $name;
        protected 
    $pattern;
        protected 
    $defaults;
        protected 
    $requirements;

        private 
    $parts;
        private 
    $regexp;
        
        const 
    RX_DEFAULT_REQUIREMENT '[^/?#]+';    
        
        const 
    NAME_INTRODUCER ':';
        const 
    RX_NAME '[a-zA-Z_-][a-zA-Z_0-9-]*';
                
        function 
    __construct($name$pattern, array $defaults = array(), array $requirements = array())
        {
            
    $this->name $name;        
            
    $this->pattern $pattern;
            
    $this->defaults $defaults;
            
    $this->requirements $requirements;
        }
        
        protected function 
    getParts()
        {
            if (!isset(
    $this->parts))
                
    $this->parts preg_split('@'.self::NAME_INTRODUCER.'('.self::RX_NAME.')@',
                            
    ltrim($this->pattern'/'),
                            -
    1,
                            
    PREG_SPLIT_DELIM_CAPTURE);        
            return 
    $this->parts;
        }
        
        protected function 
    getRegularExpression()
        {
            if (!isset(
    $this->regexp))
            {
                
    $rx '';
                foreach(
    $this->getParts() as $i => $match)
                {
                    if (
    $i 1)
                    {
                        
    $requirement = isset($this->requirements[$match])
                            ? 
    $this->requirements[$match]
                            : 
    self::RX_DEFAULT_REQUIREMENT;
                        
    $rx .= '(?P<'.$match.'>'.$requirement.')';
                    }
                    else
                    {
                        
    $rx .= preg_quote($match'@');
                    }
                }
                
    // Make the last / optional 
                
    if (!empty($rx))
                    
    $rx .= $rx[strlen($rx) - 1] == '/' '?' '/?';
                    
                
    $this->regexp '@^/?'.$rx.'$@';
            }
            return 
    $this->regexp;
        }
        
        
        
    // TODO: Requirements aren't applied to $url->getParameters()
        
    function isMatch(Url $url)
        {
            if (
    preg_match($this->getRegularExpression(), $url->getPath(), $v))
            {
                
    $parameters $url->getParameters();                
                
                
    // Remove all integer indexed values.
                
    foreach($v as $index => $value)
                    if (
    is_int($index))
                        unset(
    $v[$index]);
                        
                
    // Parameters in the path have priority over querystring                
                
    return array_merge($this->defaults$parameters$v);
            }
            return 
    FALSE;
        }
        
        function 
    createUrl(array $parametersUrl $url null)
        {
            
    $path '/';    
        
            foreach(
    $this->getParts() as $i => $match)
            {
                if (empty(
    $match))
                    continue;
                if (
    $i 1)
                {
                    if (isset(
    $parameters[$match]))
                    {
                        
    $path .= rawurlencode($parameters[$match]);
                        unset(
    $parameters[$match]);
                    }
                    else if (isset(
    $this->defaults[$match]))
                        
    $path .= rawurlencode($this->defaults[$match]);
                    else
                        throw new 
    Exception("Missing required parameter $match.");
                }
                else
                    
    $path .= $match;
            }
                            
            
    // Remove parameters that are at their defaults for this route
            
    if (!empty($this->defaults))
                
    $parameters array_diff_assoc($parameters$this->defaults);
                
            if (!empty(
    $parameters))
                
    ksort($parameters);
            
            if (
    $url instanceof Url)
            {
                
    $url->setPath($path);
                
    $url->setParameters($parameters);
            }
            else
            {
                
    // No scheme, host, or port... 
                
    $url = new Url($path$parameters);
            }
            return 
    $url;
        }
    }

    interface 
    RequestMapper
    {
        function 
    mapRequest(Request $request);
    }

    class 
    RoutesException extends Exception
    {
    }

    class 
    Routes implements RequestMapper
    {
        protected 
    $basePath;
        protected 
    $routes;
        
        function 
    __construct($basePath '/')
        {
            
    $this->basePath trim($basePath'/').'/';
            
    $this->routes = array();
        }
        
        function 
    connect($name$route, array $defaults = array(), array $requirements = array())
        {        
            if (isset(
    $this->routes[$name]))
                throw new 
    RoutesException("Route '$name' already exists");
            return 
    $this->routes[$name] = new Route($name$this->basePath.ltrim($route'/'), $defaults$requirements);
        }    
        
        function 
    getRoute($name)
        {
            if (!isset(
    $this->routes[$name]))
                throw new 
    RoutesException("Unknown route $name");
            return 
    $this->routes[$name];
        }
            
        function 
    mapRequest(Request $request)
        {
            
    $url $request->getUrl();
            foreach(
    $this->routes as $route)
            {
                
    $r $route->isMatch($url);
                if (
    $r !== FALSE)
                {                
    // Return Controller for this Route......
                
    }
            }
            return 
    null;
        }

    So now can define routes, such as...

    PHP Code:
                 $requestMapper = new Routes();
        
        
    $requestMapper->connect('day''/:year/:month/:day'
                        array(),
                        array(
    'year' => '\d{4}''month' => '\d{1,2}''day' => '\d{1,2}')); 
    And hand it off to the front controller todo it business.
    Off Topic:


    Another nice thing about the Route design is the help it gives in URL generation...

    PHP Code:
    $params = array('year' => 2006'month' => 2'day' => 12);
    $url $dayRoute->createUrl($params); 

  17. #17
    SitePoint Guru
    Join Date
    May 2005
    Location
    Finland
    Posts
    608
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    Another nice thing about the Route design is the help it gives in URL generation...
    I never understood how that is supposed to work. Neither did I understand your example, but that's nonetheless exactly what I've wanted to do. I'm going to study on that long and hard. Thanks a bunch.

    I never knew this, for instance: It is possible to name the subpattern with (?P<name>pattern) since PHP 4.3.3. Array with matches will contain the match indexed by the string alongside the match indexed by a number, then.

  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 Ezku
    I never understood how that is supposed to work. Neither did I understand your example, but that's nonetheless exactly what I've wanted to do. I'm going to study on that long and hard. Thanks a bunch.

    I never knew this, for instance: It is possible to name the subpattern with (?P<name>pattern) since PHP 4.3.3. Array with matches will contain the match indexed by the string alongside the match indexed by a number, then.
    Yeah, only discovered (?P<name> ) syntax recently myself whilst looking into this, makes extracting pieces very easy, and don't have to worry about extra () appearing in requirements.

  19. #19
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    Well suppose it depends on regexp comfort levels. For complete Ruby style..
    Interesting... not being 100% comfortable with regular expressions, I went on a pretty different route for my route (sorry about the pun) class. Using regex makes the class more concise, although I get the feeling that by using them you can't support all the functionality that rails does; for example, your class doesn't seem to support using * in the last component of the route to catch all the remaining bits of path.

    Going back to the topic; in rails, on top of the validation done to urls (which prevents the controller from trying to work with invalid data), the business logic validation is done in the models. You can set up a bunch of rules to validate against when you insert a record, update a record, or both; If the validation fails, the save() call returns false, and you can query the Errors object of the model to find out what went wrong. I've yet to come across a situation where this wasn't enough.
    Last edited by 33degrees; Feb 12, 2006 at 20:59. Reason: spelling... need to use preview more often

  20. #20
    SitePoint Addict
    Join Date
    Mar 2005
    Posts
    251
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ok then guys, I'll throw a few ideas into the ring.
    After a lot of messing I finally decided that user input represented a separate entity to the usual MVC split, not because they aren't part of the flow but because their influence spans all parts of the flow.

    Anyway I ended up defining forms and validation in a separate area that all layers can access. Now the correct way would probably be via an xml file, for ease of use (especially by page designers) I embedded the validation into custom attributes in xhtml.

    Now for us, this way seems to work. It means javascript and application validation can take place on the same set of rules in contrast to some of the methods outlined above where should you want to provide javascript validation you have to duplicate what's already set up in the model.

    Then at the PHP Conference last weekend in the final rushed few minutes of Pawel's talk he introduced his implementation of Aspect Oriented Programming, the theory being that many parts of a web app can be taken out of their traditional homes and called repeatedly at certain points through an application.

    One of the brief examples he showed was for handling authentication, but it maybe that validation could be handled in this way too, you setup your validation rules once and then define pointcuts (literally just a point in the code when you hand over to the aspect) which apply validation behaviour.

    Sorry if that's a bit heavy for monday morning, but I'd be interested in any ideas....

  21. #21
    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 33degrees
    Interesting... not being 100% comfortable with regular expressions, I went on a pretty different route for my route (sorry about the pun) class. Using regex makes the class more concise, although I get the feeling that by using them you can't support all the functionality that rails does; for example, your class doesn't seem to support using * in the last component of the route to catch all the remaining bits of path.
    Ah is that what * does. Should be too hard to add, if * is at the end prepend a regexp to eat the extra bits of path.

  22. #22
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Ren,

    If you may, could you post some script to show us how your Routes would work with a Controller? I like your classes too, but my regular expression knowledge is not all that good, so I to would be interested if you could explain some more (with examples of course )?

    Thanks.

  23. #23
    SitePoint Guru 33degrees's Avatar
    Join Date
    May 2005
    Posts
    707
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Ren
    Ah is that what * does. Should be too hard to add, if * is at the end prepend a regexp to eat the extra bits of path.
    There's some other, more subtle behavior, and unit testing was a big help there for me:

    PHP Code:
        function test_date_route_generation() {
            
    // our typical blog archive route
            
    $route = new Route('blog/$year/$month/$day'
                array(
    'controller'=>'blog''action'=>'show_date''month'=>null'day'=>null), 
                array(
    'year'=>'/^(19|20)\d\d$/''month'=>'/^[01]?\d$/''day'=>'/^[0-3]?\d$/')
            );        

            
    // some request params
            
    $request = array('controller'=>'blog''action'=>'show_date''year'=>'2005''month'=>'4''day'=>'15');
            
            
    // urls represent hierarchies, so changes in the hierarchy means ignoring items further down
            
    $this->assertEqual($route->generate_url($request, array('day'=>'25')), 'blog/2005/4/25');
            
    $this->assertEqual($route->generate_url($request, array('month'=>'5')), 'blog/2005/5');
            
    $this->assertEqual($route->generate_url($request, array('year'=>'2004')), 'blog/2004');

            
    // although, we have to option of overwriting part of the request to do the opposite
            
    $this->assertEqual($route->generate_url($requestnull, array('year'=>'2004')), 'blog/2004/4/15');        
            
            
    // if the new values are identical to the request, we need to return a url for the complete request
            
    $this->assertEqual($route->generate_url($request, array('year'=>'2005')), 'blog/2005/4/15');
            
            
    // params need to match the requirements as well, so this shouldn't work
            
    $this->assertFalse($route->generate_url($request, array('year'=>'2100')));
            
        } 

    Quote Originally Posted by Dr Livingstone
    If you may, could you post some script to show us how your Routes would work with a Controller?
    I imagine his works similarily to mine:

    PHP Code:
    /**
     * Dispatches requests to the appropriate action controller
     *
     * @package controller
     */
    class Dispatcher {

        
    /**
         * Dispatches the request
         */
        
        
    function dispatch() {
            if (!
    headers_sent($file$line)) {
                
    session_start();
            } else {
                
    trigger_error("Session could not be started because output was started in '$file' on line $line"E_USER_WARNING);
            }
            
            
    $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);
            }
            
            if (
    is_a($response'Response')) {
                
    $response->out();
                die();
            }
        }
    }

    /**
     * The Router takes care of analyising the request and returning the appropriate controller
     * it also dumps the nice url paramters into the request object
     *
     * @package controller
     * @subpackage route
     */
    class Router {
        
        
    /**
         * Maps a request to a specific controller and takes care of loading it
         * 
         * @param Request $request The object representing the incoming request
         * @return Controller The controller that will handle the request
         */
        
    function map(& $request) {
            
            
    $url $request->get['url'];
            
            
    $path_params Route::parse($url);
            
            if (!isset(
    $path_params['controller'])) {
                
    trigger_error("The mapper couldn't find a controller for the request '$url', please check the routings file"E_USER_ERROR);    
                return 
    false;
            } 

            
    $request->set_path_parameters($path_params);
            
            if (isset(
    $path_params['module'])) {
                
    $controller_path $path_params['module'].'/';    
            } else {
                
    $controller_path '';
            }
            
            
    $controller_path .= $path_params['controller'];
            
            return 
    Controller::factory($controller_path);
            
        }
        

    I could post my Route class as well, but it's even harder to understand than Ren's, and I need to refactor out some (now embarassing) static calls

  24. #24
    SitePoint Wizard Ren's Avatar
    Join Date
    Aug 2003
    Location
    UK
    Posts
    1,060
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Yes, essentially a controller class name has to come from somewhere, in Routes' case its either from

    1) the path give an pattern of /:controller/foo then /blog/foo would translate to blog controller. (PS dont really like the idea of having controller name in the URL atm, trying to figure out a neater method of mapping :controller value to an actual class name, and why I omitted it in the first post)

    2) the defaults array for the route, eg array('controller' => 'blog')

    The request mapper then just plucks the name from the array, see if its valid, perform some request fixing up, so the further controller will see the extra variables from the path, and the current route.

    PHP Code:
        $r $route->isMatch($url);
        if (
    $r !== FALSE)
        {                
    // extract the controller name
    $className $r['controller'];

    if (..
    some test to see if $className is valid controller)
    {
        
    // Add the route to the array so other actions/commands/controllers determine which route was requested. 
        
    $r[self::ROUTES_ROUTE] = $route;

        
    // rewrite our request array with the extra information gathered from path
        
    $request->setArray($r);

        
    $controller = new $className();

        return 
    $controller;


  25. #25
    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 33degrees
    PHP Code:
            // urls represent hierarchies, so changes in the hierarchy means ignoring items further down
            
    $this->assertEqual($route->generate_url($request, array('day'=>'25')), 'blog/2005/4/25');
            
    $this->assertEqual($route->generate_url($request, array('month'=>'5')), 'blog/2005/5');
            
    $this->assertEqual($route->generate_url($request, array('year'=>'2004')), 'blog/2004'); 
    Interesting, have you had the need use this functionality?


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
  •