Custom URLs from the Site Root

By Erik Schaeffer (PHPdiddy)
A number of sites nowadays are offering custom, unique URLs for people. I'm sure we can all think of one small social networking site that does this. I had a need to do this within a new Cake app I'm writing. Plus, since I'm migrating from an existing site, this is a legacy feature that must be supported.
A number of sites nowadays are offering custom, unique URLs for people. I'm sure we can all think of one small social networking site that does this. I had a need to do this within a new Cake app I'm writing. Plus, since I'm migrating from an existing site, this is a legacy feature that must be supported. I had to find a way to make http://www.example.com/username route to http://www.example.com/users/view/username.

I will say that I don't know if this is the best solution, but it is the one I currently have working. It does require you to do some extra work whenever you're creating new controllers, but the work is minimal. Also, this tutorial will only allow one of your controllers to have this functionality.

All these changes take place inside of cake/app/config/routes.php. For this example, we'll assume you have a collection of users and your typical URL for viewing a user is http://www.example.com/users/view/username. Let's also assume you have two other controllers named items and images. Finally, this assumes you have a page called error, accessible at /pages/error.

First off, we need to create the route for our custom urls. The route you would need to use is as follows:

Download code
$Route->connect('/*', array('controller' => 'users', 'action' => 'view'));

What this route says is that for anything that comes in, you will use the users controller and the view action for that controller, essentially making the entire url a list of parameters.

We have to be careful where this goes in routes.php, as this is basically a catch all for any URL given. We have to be sure this is the last route used:

Download code
<?php
// cake/app/config/routes.php

// Default route
$Route->connect('/', array('controller' => 'pages''action' => 'display''home'));

// Default pages route
$Route->connect('/pages/*', array('controller' => 'pages''action' => 'display'));

// Custom URL route
$Route->connect('/*', array('controller' => 'users''action' => 'view'));

?>

As you can see, the two default routes come first with our new route coming last.

We're not finished here yet. Since we now have this catch all in place, we need to ensure that all our controllers are treated correctly. To do so for our controllers, we add the following to routes.php:


Download code
<?php
// cake/app/config/routes.php

// Default route
$Route->connect('/', array('controller' => 'pages''action' => 'display''home'));

// Set default controller routes
$Route->connect('/users/:action/*', array('controller' => 'users'));
$Route->connect('/items/:action/*', array('controller' => 'items'));
$Route->connect('/images/:action/*', array('controller' => 'images'));

// Default pages route
$Route->connect('/pages/*', array('controller' => 'pages''action' => 'display'));

// Custom URL route
$Route->connect('/*', array('controller' => 'users''action' => 'view'));

?>

Because of our new route, anything not matching the base path route or the pages route will be routed through the view action of the users controller. We need to ensure we grab URLs that should maintain Cake's default routing.

The final step in this process would be to do some error handling. Since we're using our catch all, anything that doesn't exist is still going to try to route to our users controller. A quick bit of work on the view function of our controller will redirect the user if an invalid URL is given:

Download code
function view($username)
{
    if ($user = $this->User->findByUsername($username)) {
        $this->set('user', $user);
    } else {
        $this->redirect('/pages/error');
        exit();
    }
}

If we can find the username given, $user will be set and we can then set 'user' in our view. If not, redirect them to an error page.

That's all there is to it. I'm sure there are more ways to tweak this strategy, but for now, it seems to work pretty well.

 

Comments 86

CakePHP Team Comments Author Comments
 

Comment

1 Another approach...

I use this sometimes, especially in small projects:

$Route->connect ('(?!admin|items|images)(.*)', array('controller'=>'users', 'action'=>'view'));

It can be translated like this: when it doesn't have to deal with controllers like items or images, or admin panel, then it treats all the urls with controller users, action view.
Posted Oct 6, 2006 by Lucian Lature
 

Comment

2 Return of the tilde

You can also do this:

$Route->connect('/~(.*)', array('controller' => 'users', 'action' => 'view', '$1'));

to accept urls like http://www.example.com/~username
Posted Oct 7, 2006 by Jayson Harshbarger
 

Question

3 Cake Version

Which version of Cake are you guys talking about? As far as I know only Cake 1.2 supports regular expressions in routes. Am I wrong?
Posted Oct 10, 2006 by Mladen Mihajlovic
 

Comment

4 Routing without redirecting

This solution is fine if it's acceptable to you that the browser address field is redirected as soon as www.domain.com/username is entered. If this is not acceptable because you want to communicate a clear and consistent url structure to your users, you will have to turn to mod_rewrite after all. That is, unless I overlook some alternatives...
Posted Oct 14, 2006 by tawm
 

Comment

5 Correction to former comment

I appeared to be incorrect about routes being redirects. Therefore the solution described in this article is suited very well for communicating clear and consistent urls to users.
Posted Oct 14, 2006 by tawm
 

Question

6 This means admin routing wont work

I have done something very similar but the problem is it mucks up the admin routing.

http://manual.cakephp.org/chapter/configuration section 4

basically you have

// Custom URL route
$Route->connect('/*', array('controller' => 'users', 'action' => 'view'));

then type in an admin url like

www.mysite.com/admin/items/add
this will look for a user admin rather than looking for a action admin_add inside the items controller.

Is there any way to solve this?

One method i though of was just to scrap the define('CAKE_ADMIN', 'admin');

and write admin routes for each controller.

$Route->connect('admin/items/:action/*', array('controller' => 'items'));

Posted May 14, 2007 by anton morrison
 

Comment

7 found the answer

Hi just in case anyone was having the same problem as me i found you can pass a var 'admin' => '1' and that tell cake to use the admin action.

So

$Route->connect('admin/items/:action/*', array('controller' => 'items', 'admin' => '1'));

will use the $item->admin_:action

amazing!
Posted Jul 27, 2007 by anton morrison
 

Comment

8 Perfect for what I need.

I use this sometimes, especially in small projects:

$Route->connect ('(?!admin|items|images)(.*)', array('controller'=>'users', 'action'=>'view'));

It can be translated like this: when it doesn't have to deal with controllers like items or images, or admin panel, then it treats all the urls with controller users, action view.

Wow, thanks Lucian this piece of code is brilliant.
Posted Aug 9, 2007 by Marc Grabanski
 

Comment

9 admin wildcard routes

I use this sometimes, especially in small projects:

$Route->connect ('(?!admin|items|images)(.*)', array('controller'=>'users', 'action'=>'view'));

It can be translated like this: when it doesn't have to deal with controllers like items or images, or admin panel, then it treats all the urls with controller users, action view.
Fantastic!
Posted Nov 25, 2007 by Giorgi