Vanja D.
Vanja D.

Reputation: 854

CakePHP branch routing

I am trying to test some new features on a live site.

My goal is to have a prefix ('dev', for example) and have that prefix display the complete site with default actions, except those I define as dev_{action_name}.

Example:

/dev/orders/create

Seeks for OrdersController::dev_create() action, if not found, uses OrdersController::create().

So far, I managed to make a standard 'dev' prefix with the Router and put some beforeFilter magic like:

<?php
//bootstrap.php code:
Configure::write('Routing.prefixes', array('admin', 'dev'));

// AppController::beforeFilter() code:
if (preg_match('/^dev_(.*)$/', $this->params['action'], $subs)){
   $new_action = $subs[1];
   if (method_exists($this, $new_action)){
          $this->params['action'] = $new_action;
    } 
}
?>

This works as expected, except the complete Routing schema is not copied to "dev", which means all the links are rendered without the "dev prefix and my custom routes are disregarded.

I thought about fixing it with .htaccess rule and putting a dev=true in the query string:

#webroot/.htaccess
RewriteRule ^dev(.*)$ index.php?dev=true&%{QUERY_STRING}
#... rewrite conds ...
RewriteRule ^(.*)$ index.php [QSA,L] #original Cake rewrite

But this yielded no results (Cake was still parsing the original URL, not the re-written).

Is there a "right" way of doing this?

Upvotes: 1

Views: 303

Answers (2)

Vanja D.
Vanja D.

Reputation: 854

I have successfully achieved this with CakePHP 2.2.1. It's ugly, it's long, it's against the rules, but it works! :D

file: /app/webroot/.htaccess

RewriteEngine On
RewriteRule ^dev/(.*)$ $1 //my hack
    //      /dev/orders/create => /orders/create 
    //      /dev/css/main.css => /css/main.css
    // This only changes the url for the next rule
    // Cake would have still parsed /dev/orders/create
    // but this is needed for the sake of static files in app/webroot
//cake native rewrite
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php [QSA,L]

file: bootstrap.php

$url = (!empty($_SERVER['REQUEST_URI'])) ? $_SERVER['REQUEST_URI'] : ''; 
    //$url is still /dev/orders/create, despite the rewrites

Configure::write('App.dev', false); 
//we use this to check if we're in "dev" mode
//anywhere in the app, just call: 
    //  if ( Configure::read('App.dev') ) ...

//check if URL starts with /dev
if (preg_match('|^/dev|', $url)){ 
    /* the most important thing is to trick CakePHP
    into thinking it's on a /dev/ subfolder on the server
    this preserves all the routing as it 
            should be, not making it prefixed route */
    Configure::write('App.base', '/dev');
    Configure::write('App.dev', true);
    //this changes /dev/orders/create to /orders/create 
            //for Cake's Route parser
    $_SERVER['REQUEST_URI'] = preg_replace('|/dev|', '', $_SERVER['REQUEST_URI']); 
}
?>

At this point, we have all that we need to have CakePHP display a complete copy of the site under /dev/. Cake does all the default actions+views, all links work as expected (as long as you use helpers for URLs), all static files get served. All app paths and constants remain intact.

Now for some method magic:

<?php
//file: AppController.php
public function invokeAction(CakeRequest $request) {

    if (Configure::read('App.dev')) {
        $new_action = 'dev'.$request->params['action'];
        if (method_exists($this, $new_action)){
            $request->params['action'] = $new_action;
        }
    }


    return parent::invokeAction($request);
}
?>

All of this enables us to:

  • Have a complete working alias of the site on /dev/, which includes routes, prefixes, static files, database connections, etc.
  • Override an ACTION using "dev" in front of the name.
  • Have a single switch for "dev" or "not dev" (App.dev config)

For instance:

<?php
//OrdersController.php

//gets called on /admin/orders/view
public function admin_view(){} 

//gets called on /dev/admin/orders/view
public function devadmin_view(){ 
    //views have to be defined manually for DEV actions
    $this->render('devadmin_view'); 
}

//gets called on /admin/orders/add 
//            AND /dev/admin/orders/add
public function admin_add() {} 

?>

Upvotes: 1

jeremyharris
jeremyharris

Reputation: 7882

Something like this ought to do what you want. Before invoking the action it checks if it exists and if not falls back to the original action. If you did it later exceptions would be raised since the action doesn't exist.

There may be more to it but this is a start.

// need to override invokeAction
public function invokeAction(CakeRequest $request) {
  if ($request->prefix == 'dev') {
    if (!method_exists($request->action)) {
      $request->action = str_replace($request->prefix.'_', '', $request->action);
      $request->prefix = null;
    }
  }
  return parent::invokeAction($request);
}

Upvotes: 1

Related Questions