Permanent appended named parameters in routes with CakePHP

For a project i’m working on i needed a bit of custom routing.
Basically the project is a shop where you can edit multiple events and i stored the current event in a session.
But the request came if the client could edit multiple events at the same time.
Long story short, i needed the event_id somewhere in the url and this is where the router comes in to play.

There are a couple of ways to get the event_id in the url with custom routing.

Option one

Use a named parameter

this method will append event_id:[UUID] to the url.
for example: http://www.example.com/products/index/event_id:490ed5ab-ed6c-4034-8205-0264d5453078

in /app/config/routes.php

Router::connectNamed(array('event_id'=>$UUID), array('default' => true, 'greedy' => false));

This will let the router know that we have a named part called event_id.
It will also check if event_id is an UUID (other possibilities are $ID, $day, $month and $year, or a regular expression).

Helper

This was the easy part, because you need to tell all the helpers that create an url to use ‘event_id’=>’[UUID]‘.
In my case i already wrote an app and i didn’t feel like rewriting all links and forms, so i created a file called app_helper.php in the app folder like so:

/app/app_helper.php

<?php
class AppHelper extends Helper { 
        function url($url = null, $full = false) { 
                if (is_array($url)) { 
                        if (empty($url['event_id']) && isset($this->params['named]['event_id'])) { 
                                $url['event_id'] = $this->params['named]['event_id']; 
                        }
                } 
                return parent::url($url, $full); 
        } 
} 
?>

What this does is append the event_id to all url arrays and then calls the parent url function (in /cake/lib/helper/helper.php).
Because all helpers extend AppHelper, all url calls will go tough the above function.

Redirect

Now we only need to address one more issue, namely the $this->redirect(); function in the controllers, because we want the event_id appended to those url’s too.

/app/app_controller.php

function redirect($url, $status = null, $exit = true) { 
 	if (is_array($url)) { 
        if (empty($url['event_id']) && isset($this->params['named]['event_id'])) { 
        	$url['event_id'] = $this->params['named']['event_id']; 
        }
 	}
    return parent::redirect($url, $status, $exit); 
}

All controllers extend app_controller so they will all use this new redirect function.

So once you add event_id:[UUID] to an url, it will always be appended.
the event id can be retrieved in all controllers by calling $this->params['named']['event_id'] or $this->passedArgs['event_id']

Option two

The other way to do this is by changing a lot of routes in the routes.php file, but the url will be prettier (like: http://www.example.com/event_id/controller/action/params)

(/app/config/routes.php)

/*
	 * The following section is to add the event_id to each backend url
	 * The event_id is added in the url method of app_helper.php and redirect method of app_model.php
	 */
	Router::connect('/:event_id/:controller', 
			array('controller'=>':controller'), array('event_id'=>$UUID));

	Router::connect('/:event_id/:controller/:action', 
			array('controller'=>':controller', 'action'=>':action'), array('event_id'=>$UUID));

	Router::connect('/:event_id/:controller/:action/:id', 
			array('controller'=>':controller', 'action'=>':action', 'id'=>':id'), array('pass'=>array('id'),'event_id'=>$UUID));

	Router::connect('/:event_id/:controller/:action/:id/*', 
			array('controller'=>':controller', 'action'=>':action', 'id'=>':id'), array('pass'=>array('id'), 'event_id'=>$UUID));

	Router::connect('/:event_id/:controller/:action/*', 
			array('controller'=>':controller', 'action'=>':action'), array('event_id'=>$UUID));

I’m not shure if this is the most efficient way to cover all routes, but if you replace the code from the first block with this code, you get the nice url (http://www.example.com/event_id/controller/action/params).

The last array makes shure only uuid’s are used by the router.

Filed Under: News - read on

Admin routes

I had some issues today with the admin routes in the new cake version.

The problem was that i was using an admin route in the core (/app/config/core.php)

/**
 * Uncomment the define below to use CakePHP admin routes.
 *
 * The value of the define determines the name of the route
 * and its associated controller actions:
 *
 * 'admin' 		-> admin_index() and /admin/controller/index
 * 'superuser' -> superuser_index() and /superuser/controller/index
 */
Configure::write('Routing.admin', 'dashboard');

And in my routes file (/app/config/routes.php) i was using this:

Router::connect('/dashboard/', array('controller' => 'sites', 'action' => 'index', 'admin'=>'dashboard'));

This didn’t work out as planned, it gave an error about not beeing able to find a controller action (index(); instead of dashboard_index();

Turns out that in the new version you need to use prefix instead of admin, so the line should be:

Router::connect('/dashboard/', array('controller' => 'sites', 'action' => 'index', 'prefix'=>'dashboard'));

Then in my app_controller (/app/app_controller.php) i had a function to switch to the admin layout if it was called in the url, but this changed also.

It used to be:

if (isset($this->params['dashboard'])) {
		$this->layout = 'dashboard';
	}

But i had to change it to:

if (isset($this->params['prefix']) && $this->params['prefix'] == 'dashboard') {
		$this->layout = 'dashboard';
	}

The reason for this is that it uses the prefix value from the routes file.
If you use the first example (the one with $this->params['dashboard']) the dashboard layout will not be set if there is no / after dashboard in the url (http://mysite.com/dashboard), because $this->params['dashboard'] will be empty.
Using the second example it will set the layout.

I also check if the prefix is dashboard, because you could be using more then one prefix.

Filed Under: CakePHP, Code, English - read on