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.
We need some sample data to work with. Follow the Getting ready section of the recipe Adding catch-all routes for profile pages.
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') ) );
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); } } ?>
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.
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:
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. 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.
3.138.122.11