Stop ACTA

Advanced routing with CakePHP - one example

Posted in CakePHP on 03.06.2009.

Ever wondered how to make a route which has no controller/action name yet doesn't break your existing routes?

Let's say you have an app which has some promo codes, like so:

http://example.com/get-free-stuff-23999

or simply:

http://example.com/whatever

How do you tell cake this is not a controller and how to avoid the "missing controller" error? Answer: RegEx!

You might think it's very simple to do something like this. Like so:

Router::connect
    (
        '/:promo',
        array
        (
            'controller' => 'codes',
            'action' => 'process'
        ),
        array
        (
            'promo' => '[a-zA-Z\-_]+'
        )
    );

But there's a problem with that. Assuming you also have an ArticlesController, what happens when someone enters:

http://example.com/articles

Bad stuff. And you don't want to enter a special route for all controllers, don't you?

Here's something I came up with, it seems to be working but no guarantees..

$controllers = Cache::read('controllers_list');

if ($controllers === false)
{
    $controllers = Configure::listObjects('controller');

    foreach ($controllers as &$value)
    {
        $value = Inflector::underscore($value);
    }

    $controllers = implode('|', $controllers);

    Cache::write('controllers_list', $controllers);
}

Router::connect
    (
        '/:promo',
        array
        (
            'controller' => 'codes',
            'action' => 'process'
        ),
        array
        (
            'promo' => '(?!('.$controllers.')\W+)[a-zA-Z\-_]+/?$'
        )
    );

Easy right? Simple negative lookahead to make sure we're not grabbing all the routes including other controllers, and a simple RegEx to match all the characters we want to use in our "promo code".

I assume there are some limitations to this method, but it might also be useful to someone.

Happy baking!

Article comments — View · Add


Page 1 of 4
1 · 2 · 3 · 4

Lennaert :: 04.06.2009 06:39:22
Wow very useful! Thanks!
Matt Curry :: 18.06.2009 13:03:14
Nice trick. My only suggestion would be to wrap the listObjects line with a cache check. listObjects uses the files to get the list of controllers. Since controllers don't change very often it's a waste to do the check on every request.
lecterror :: 18.06.2009 13:10:44
Absolutely.

I actually did this to answer someone's question on StackOverflow, and I did recommend caching. I sure hope he did it... :)
Ben Pesso :: 19.06.2009 00:14:46
Excellent trick! Perhaps update the post with Matt's suggestion for a more complete example?
lecterror :: 19.06.2009 00:36:27
I've updated the article, thanks for your suggestions!