Custom URLs from the Site Root

This article is also available in the following languages:
By 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:


$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:


<?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:



<?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:


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

  • Posted 11/07/10 03:22:03 AM
    Thank you lucian for providing this nice regex! I recognized that it also matches /imagesUUUU/ or /adminIOOI/ which is obviously not wanted, because you want to match exactly the controller.
    So I use word boundaries \b to match the exact word.
    (?!\badmin\b|\bitems\b|\bimages\b)(.*)

    I actually wanted a Route that is dynamic. Everything from
    http://www.example.org/* should be routed to one controller (users) EXCEPT every URL that starts with a existing controller! And this controller should match EXACTLY (with lowercase and uppercase first character). So heres my full code:

    //get all controllers
    $Configure = &Configure::getInstance();
    $controllerList = $Configure->listObjects('controller');
    //add prefixes (neccessary because admin might just be prefix and not controller)
    $controllerList[] = 'Admin';
    $controllers = '\b'.implode('\b|\b', $controllerList);
    $controllers .= '\b|\b'.low(implode('\b|\b', $controllerList)).'\b';
    //print($controllers);


    Router::connect('/:pageName/*',
    array(
    'controller' => 'users',
    'action' => 'index'
    ),
    array(
    'pageName' => '(?!'.$controllers.')(.*)'
    )
    );

    There could be a bug if your controllername is like pageTests, but for my project it doesn't matter.
  • Posted 09/18/10 03:18:02 AM
    Hi,

    I think there's some routing problem in my cake.

    This URL below should display an image but it's trying to rendering a view now.

    http://www.in-culture.info/fckfiles/image/nw.jpg
    Here's my full question

    http://stackoverflow.com/questions/3738001/cakephp-is-interrupting-images
    Thanks for helping,
    MOe
  • Posted 11/22/09 01:37:19 PM
    wow! just found it could be done using 'prefix' too

    Router::connect('/admin/', array('controller' => 'users', 'action' => 'login', 'prefix' => 'admin'));
  • Posted 11/03/09 03:35:02 AM
    I used John Fitzgerald's solution. But It doesn't work on admin section. So you should just combine with anton morrison's solution.

    Just change last line.
    Router::connect('/*', array('controller' => 'members', 'action' => 'show'));
    to
    Router::connect('(?!admin|items|images)(.*)', array('controller' => 'members', 'action' => 'show'));
  • Posted 08/21/09 06:36:52 AM
    Hi guys!

    Took about a week looking for information on how to customize the URL from the paginate method.

    I found several things but nothing really useful, and the truth that I am a little off and I saw that you work with CakePHP.
    The issue is that I have to keep the urls of my site just because they are indexed in Google and the SEO and the structure of URLs is very well composed. For example, I have a url like:
    http:miproyecto.loc/padres
    which I included in a routes.php enroute to do the following:

    Router::connect ('/padres', array('controller'=>'contenidos', 'action'=>'listado','parameters'=>'listar_todo'));

    and when to call the method paginate:

    $array_contenidos = $this->paginate('Contenido',$criterios);

    the url that the paginate method looks like:

    http://miproyecto.loc/contenidos/listado/page:2
    but I want to get
    http:miproyecto.loc/padres-2.

    I tried everything and I managed to change the urls generated by the method of pagination In the function link of the helper paginator.php I have included the following lines:

    // new url code
    $seccion_url = explode("-",$this->params['url']['url']);
    $url = 'http://'.DOMAIM.'/'.$seccion_url[0].'-'.$url['page'];

    return $this->{$obj}->link($title, $url, $options);

    But now I have the problem to put in all the rules (manually) routes.php (which I can die because there are thousands)
    Router:: connect ( '/ preconception-2', array ( 'controller' => 'content', 'action' => 'list', 'parameters' =>' listar_todo ',' named '=> array (' page '=> 2)));

    I hope you can help me and give some advice or instructions.

  • Posted 05/18/09 12:48:39 AM
    Instead of listing all controllers one by one, you can get a list of all controllers and route them. This means if you add new ones you dont have to update the list.

    //get all controllers
    $Configure = &Configure::getInstance();
    $controllerList = $Configure->listObjects('controller');

    foreach($controllerList as $controllerName)
    {
    //map all controllers (apart from app and pages to their name
    if($controllerName != "App" & $controllerName != "Pages")
    {

    //route the normal name
    Router::connect('/' . $controllerName . '/:action/*', array('controller' => $controllerName));

    //get the name with first letter lower
    $firstLetterLower = strtolower(substr($controllerName,0,1));
    $lowerCaseName = $firstLetterLower . substr($controllerName,1);

    //route the name with first letter lowered
    Router::connect('/' . $lowerCaseName . '/:action/*', array('controller' => $lowerCaseName));
    }
    }
    • Posted 03/17/10 07:39:49 PM
      Instead of listing all controllers one by one, you can get a list of all controllers and route them [...]
      YESSSSSSS! This is exactly what I was looking for. I had no idea one could retrieve the controllers like that. This piece of code has enabled me to finally reach my goal of not having "/pages/" in the URL and not having to manually route everything.

      Thank you very much John!
  • Posted 07/27/07 12:22:07 PM
    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 05/14/07 11:33:54 AM
    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 11/22/09 01:30:04 PM
      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'));


      I was looking for a different thing which I've got from anton morrison's answer. For my application's admin panel when I browse to the admin panel http://abcoder.com/admin I see an error. In fact I want to show the log in page there which is actually from http://abcoder.com/admin/users/login. I did not know I was missing the 'admin' => '1' part. How do you guys know that much about cakePHP! In the whole manual there was no 'admin' => '1' ! :(
      This is what I used for my need:
      Router::connect('/admin/', array('controller' => 'users', 'action' => 'login', 'admin' => '1'));
      Thank you anton morrison.
  • Posted 10/14/06 10:03:02 PM
    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 10/14/06 08:59:08 PM
    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 10/10/06 03:12:46 AM
    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 10/07/06 12:09:06 PM
    You can also do this:

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

    to accept urls like http://www.example.com/~username
  • Posted 10/06/06 01:49:30 PM
    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 11/25/07 06:53:45 AM
      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 08/09/07 11:12:52 PM
      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.

Comments are closed for articles over a year old