Creating custom Route classes

In the recipe Adding catch-all routes for profile pages we created routes so that profile pages can be accessed specifying only the user name in the URL. However, that implementation had a problem: we had to disallow the automatic access of the index action.

This recipe shows a different approach to our profile URL generation, by creating a custom route implementation that not only overcomes this problem, but makes sure the route is utilized only for existing profile records.

Getting ready

We need some sample data to work with. Follow the Getting ready section of the recipe Adding catch-all routes for profile pages.

How to do it...

  1. Edit your app/config/routes.php file and add the following routes at the end of the file:
    App::import('Lib', 'ProfileRoute'),
    Router::connect('/:userName',
    array('controller' => 'profiles', 'action' => 'view'),
    array(
    'routeClass' => 'ProfileRoute',
    'pass' => array('userName')
    )
    );
    
  2. Now create a file named profile_route.php and place it in your app/libs folder, with the following contents:
    <?php
    App::import('Core', 'Router'),
    class ProfileRoute extends CakeRoute {
    public function match($url) {
    if (!empty($url['userName']) && $this->_exists($url['userName'])) {
    return parent::match($url);
    }
    return false;
    }
    public function parse($url) {
    $params = parent::parse($url);
    if (!empty($params) && $this->_exists($params['userName'])) {
    return $params;
    }
    return false;
    }
    protected function _exists($userName) {
    $userNames = Cache::read('usernames'),
    if (empty($userNames)) {
    $profiles = ClassRegistry::init('Profile')->find('all', array(
    'fields' => array('username'),
    'recursive' => -1
    ));
    if (!empty($profiles)) {
    $userNames = array_map(
    'strtolower',
    Set::extract('/Profile/username', $profiles)
    );
    Cache::write('usernames', $userNames);
    }
    }
    return in_array($userName, (array) $userNames);
    }
    }
    ?>
    
  3. Next, edit your app/models/profile.php file and add the following methods to the Profile class:
    public function afterSave($created) {
    parent::afterSave($created);
    Cache::delete('usernames'),
    }
    public function afterDelete() {
    parent::afterDelete();
    Cache::delete('usernames'),
    }
    

You can now browse to http://localhost/john to see John's profile page. Specifying an invalid name in the URL (such as http://localhost/kate) would produce the regular CakePHP error page, while browsing to http://localhost/profiles will correctly take us to the profile index page.

How it works...

We start by first importing our custom route class file, and then defining a catch-all route for the view action of the profiles controller, using the custom ProfileRoute class, and setting the userName route element to be passed as a regular argument.

The ProfileRoute implementation implements two of the most typical route class methods:

  1. match(): It is used during reverse routing to convert an array-based URL into its string representation. If the method returns false, then the provided URL does not fall into this route.
  2. parse(): It is used when parsing a requested URL into an array-based URL, specifying controller, action, and other parameters. If the method returns false, then this tells CakePHP that the given URL is not handled by this route.

We created a helper method, called _exists(), to assist us, which looks for the given username amongst the registered records. We cache the list of usernames for obvious performance reasons, and we invalidate this cache whenever a record is created, modified, or deleted, by implementing the afterSave and afterDelete callbacks in the Profile model.

Our match() implementation first checks to make sure the userName route element is provided. If so, and if the given user exists, it will use the parent implementation to return the string representation. In any other case (no username provided, or nonexistent), it will not process the given URL.

The parse() implementation starts by calling its parent implementation to convert the string URL into an array based URL. If that call is successful (which means it contains the userName route element), and if the given user name exists, it returns the conversion. Otherwise it returns false to not process the given URL. Another route handler, or CakePHP's default route handler, will process it.

See also

  • Adding catch-all routes for profile pages
  • Custom route classes
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.122.11