Redirecting in your CakePHP app
My CommonComponent now contains three redirecting methods:
/**
* @param mixed $url
* @param bool $useReferer
* returns nothing and automatically redirects
* 2010-11-06 ms
*/
public function autoRedirect($whereTo, $useReferer = true) {
if ($useReferer && $this->Controller->referer() != '/' . $this->Controller->params['url']['url']) {
$this->Controller->redirect($this->Controller->referer($whereTo, true));
} else {
$this->Controller->redirect($whereTo);
}
}
/**
* should be a 303, but:
* Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
* @see http://en.wikipedia.org/wiki/Post/Redirect/Get
* @param mixed $url
* TODO: change to 303 with backwardscompatability for older browsers?
* 2011-06-14 ms
*/
public function postRedirect($whereTo, $status = 302) {
$this->Controller->redirect($whereTo, $status);
}
/**
* only redirect to itself if cookies are on
* prevents problems with lost data
* Note: Many pre-HTTP/1.1 user agents do not understand the 303 status. When interoperability with such clients is a concern, the 302 status code may be used instead, since most user agents react to a 302 response as described here for 303.
* @see http://en.wikipedia.org/wiki/Post/Redirect/Get
* TODO: change to 303 with backwardscompatability for older browsers?
* 2011-08-10 ms
*/
public function prgRedirect($status = 302) {
if (!empty($_COOKIE[Configure::read('Session.cookie')])) {
$this->Controller->redirect('/'.$this->Controller->params['url']['url'], $status);
}
}
autoRedirect and postRedirect
Where do I use it? In pretty much every form (for instance edit action):
if (empty($id) || !($user = $this->User->find('first', array('conditions'=>array('User.id'=>$id))))) {
$this->Common->flashMessage(__('invalid record', true), 'error');
$this->Common->autoRedirect(array('action' => 'index'));
}
if (!empty($this->data)) {
if ($this->User->save($this->data)) {
$var = $this->data['User']['id'];
$this->Common->flashMessage(sprintf(__('record edit %s saved', true), h($var)), 'success');
$this->Common->postRedirect(array('action' => 'index'));
} else {
$this->Common->flashMessage(__('formContainsErrors', true), 'error');
}
}
if (empty($this->data)) {
$this->data = $user;
}
The autoRedirect automatically redirects back to the site the user came from if possible (if the user clicked a link). Otherwise it will use the provided fallback url.
The postRedirect is a wrapper for the future where one day 303 can be used without causing trouble (read further for details).
prgRedirect
Why is this necessary in some forms? Most search forms do a simply post. What they should do is a POST + GET afterwards. Thats called PRG pattern and is described here.
Especially after posting search forms or entering data you want to avoid a nasty message like some modern browsers produce if you then hit the back button. You want to graciously display the page prior to the post. Thats where this extra redirect comes into play. Always redirect after a post – quite easy to remember.
if (!empty($this->data)) {
if ($this->Model->search($this->data)) {
# save POST search to session, redirect and display the search result as GET
$this->Common->prgRedirect();
}
}
if ($search = $this->Session->read(...)) {
...
}
As the comment in the method head as well as other sources explain one should NOT use 303 or you end up with broken forms for some users.
Note the failsafe with the cookie. If cookies are disabled this would result in empty sessions and therefore never work. For disabled cookies there cannot be a redirect and therefore needs the POST to display the data.
Therefore your forms should work with both POST and GET for this very same reason. The "prg" redirect is only an enhancement to provide better functionality in the normal use case (where users do use the back button).
Moved/deleted content
If you moved or deleted some content you can use the 301 redirect to tell search engines and browsers where to find the same content at the new location or where to go to instead:
if (empty($manufacturer)) {
$this->Session->setFlash(__('Invalid Manufacturer', true));
$this->redirect(array('action'=>'index'), 301);
}
In the example I use this to redirect from views back to index if no manufacturer (retrieved via slug) was found. This is necessary to avoid duplicate content for invalid slugs.
Complete list of browsers that are not capable of handling 303s
//TODO – does anyone have infos on that matter?