Secrets of Admin Routing

by nate
During the CakeFest workshop in Berlin, one of the many topics covered was the routing system. While we touched on most of the general usage patterns, one of the features we didn't go into depth on was admin routing. Unsurprisingly, this is the feature we received the most requests for more information on.
In CakePHP, admin routing is pretty simple: you turn it on, you turn it off, you change the prefix. But there's not much else you can do with it. Right?

I'm about to reveal a little secret about this piece of code:
Configure::write('Routing.admin', 'admin');

It's pretty much irrelevant.

Initially (i.e. 1.1 era and prior), admin routing was fairly static. However, it was refactored in 1.2 such that you could create admin routes (now called 'prefix routes') the same way you create normal routes. The configuration itself was maintained for backwards compatibility, but the following is almost functionally identical:

Router::connect('/admin/:controller/:action/*', array(
    'action' => null, 'prefix' => 'admin', 'admin' => true
));

I say 'almost' because the one thing that this code does not cover is plugins; but that can be corrected just as simply:

if ($plugins = Configure::listObjects('plugin')) {
    $pluginMatch = implode('|', array_map(array('Inflector', 'underscore'), $plugins));
    Router::connect(
        "/admin/:plugin/:controller/:action/*",
        array('action' => null, 'prefix' => 'admin', 'admin' => true),
        array('plugin' => $pluginMatch)
    );
}

A quick explanation of the parameters:
  • 'prefix' => 'admin' - this is a special routing key which defines the prefix that is appended to the action. Since this key modifies the way in which the request is dispatched, rather than the request itself, it is not factored into reverse routing. Which brings us to the next key...
  • 'admin' => true - because the 'prefix' key is not factored into reverse routing, we need a custom flag to differentiate the route, and give us something to check when a request hits this route. While the name of this key matches the value of 'prefix' by convention, this flag can actually be anything, so long as it is not user-modifiable (i.e., not present in the URL template itself).
  • 'action' => null - this allows the route to match with or without the action being specified (in which case it defaults to the 'index' action as normal).
  • 'plugin' => $pluginMatch - in the plugin route, this key is present in the third parameter to ensure that the :plugin only matches a valid plugin name.

Using these flags, you can create as many prefix routes for as many purposes as you want. Keep in mind, however, that these flags do not persist by default. However, you can tell Cake to persist certain parameters automatically, as in this example with PaginatorHelper:

$paginator->options(array('url' => array('admin' => true)));
echo $paginator->link('Page 2', array('page' => 2));

However, this only applies to URLs generated by PaginatorHelper. To generalize this to all helpers, you can implement a simple check in AppHelper, that works as follows:

App::import('Core', 'Helper');

class AppHelper extends Helper {

    function url($url = null, $full = false) {
        if (isset($this->params['admin']) && is_array($url) && !isset($url['admin'])) {
            $url['admin'] = $this->params['admin'];
        }
        return parent::url($url, $full);
    }
}

This code can then be extrapolated out to persist any parameters in your application.

Conclusion


Using these techniques, custom prefix routing in CakePHP has never been simpler or more flexible.

Report

More on Snippets

Advertising

Comments

  • sachy posted on 05/06/11 09:20:47 AM
    Hi nate,
    thank you for your great article.
    I found this is very useful and let Japanese CakeBaker know about this.
    Google's auto translation seems broken, so I made Japanese translation on my blog.
    http://bakery.cakephp-users.jp/?p=101
  • webbedit posted on 09/24/09 05:55:26 AM
    Nate,

    I have just applied this to a little site I am working on and all is great (using the little app_helper snippet too) with my links in the admin area automagically adding the admin_ prefix.

    The problem comes when using $this->redirect() in an action after say an add or edit, the admin attribute is being added as admin:1 at the end of my url.

    Paul.
    • thinline posted on 10/02/10 06:44:22 PM

      The problem comes when using $this->redirect() in an action after say an add or edit, the admin attribute is being added as admin:1 at the end of my url.


      If you are still using 1.2.x and need prefix routing I would suggest adding the same method that is used for the AppHelper to AppController for Controller::redirect


          /*
          * This method is used to overload the Conroller::redirect method.
          * It adds the reverse routing prefix functionality not found in CakePHP 1.2.x
          * This functionality should be re-visited after upgrading to 1.3.x
          */
          function redirect($url, $status = null, $exit = true) {

              if (isset($this->params['admin']) && is_array($url) && !isset($url['admin'])) {
                  $url['admin'] = $this->params['admin'];
              }

              parent::redirect($url, $status, $exit);
          }

  • tobi_one posted on 08/25/09 07:29:53 AM
    Hi,

    great article, thanks!
    Is it possible to disable admin routing on a specific link?
    I have tried
    $html->('link name','/some/url',array('admin' => 'false'); and
    $html->('link name','/some/url',array('admin' => 'null')

    but neither one works!
    Or is it possible to disalbe admin routing "on the fly" alltogether?

    Cheers,
    tobi_one
    • nate posted on 09/08/09 11:51:22 PM
      Hi tobi_one,

      In that example, you're passing the 'admin' flag to the HTML attributes of the link, not to the URL. In order to disable admin routing, you need to use arrays, like so:

      $html->link('link name', array('controller' => 'some', 'action' => 'url', 'admin' => false'));

      Hope that helps,
      - Nate
  • axel posted on 07/17/09 12:05:36 PM
    Hi Nate,

    thank your for the nice article.
    Can you tell me if it's possible to do the same prefix-thing with the Controllers instead of Actions? It would be nice if i can make a Controller for the Frontend and one for the Backend.

    Frontend:

    Controller Class:

    <?php 
    class NewsController extends AppController {}
    ?>

    Backend:

    Controller Class:

    <?php 
    class Admin_NewsController extends AppController {}
    ?>

    best regards, axel
login to post a comment.