Displaying custom error message with the right HTTP response codes
Posted by primeminister | Filed under Cake-Toppings
I’m building an RESTful API with CakePHP now. That is great stuff. Very easy and quick for the basics. Some more info on how to in the book and here.
But I wanted a custom XML error message with the right HTTP respond code like the Twitter API does. I knew it could be done with overwriting the AppError class and make my own custom method.
This is how I did it:
You have to add the RequestHandler component to the app_controller, because we need it. Then create a file APP/app_error.php :
<?php
/**
* app_error.php
*
* Short description
*
* Long description
*
* @package api
* @author primeminister
* @copyright 2009 Ministry of Web Development
* @date $LastChangedDate: 2009-03-31 20:55:40 +0200 (di, 31 mrt 2009) $
* @version $Rev: 13 $
**/
/**
* AppError
*
* Custom error handling methods
*
* @package api
* @author primeminister
* @version $Rev: 13 $
**/
class AppError extends ErrorHandler
{
/**
* HTTP response codes array
*
* @var array
* @see http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
**/
var $codes = array(
200 => 'OK',
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Time-out',
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Time-out'
);
}
?>
As you can see I copied the error codes from the cake/libs/controller/controller render() method and pasted it in here. I use this to serve the right message to the user.
Then add a custom method like ‘error()’:
/**
* error
*
* Handles the error with the right response code
*
* @param array $params
* @return void
* @author primeminister
* @access public
* @see http://apiwiki.twitter.com/REST+API+Documentation#HTTPStatusCodes
*/
public function error($params)
{
extract($params, EXTR_OVERWRITE);
if (!isset($name)) {
$name = $this->codes[$code];
}
if (!isset($url)) {
$url = $this->controller->here;
}
// set header
header("HTTP/1.x $code $name");
$this->controller->set(array(
'code' => $code,
'name' => $name,
'message' => $message,
'title' => $code . ' ' . $name,
'request' => $url
));
if ($this->controller->RequestHandler->isXml()) {
$this->controller->RequestHandler->renderAs($this->controller, 'xml');
}
$this->_outputMessage('error');
}
and add it to the AppError class. (I copied error() method from the core and adjusted it)
First I set some vars when it hasn’t been set in the controller. Then I serve the right response code, based on the $code var. The the RequestHandler playes a role, because if the request was an XML request we want to serve also an XML message.
So first check with RequestHandler->isXml() if that is true and call the RequestHandler->renderAs() method, otherwise it won’t serve us the right Content-Type.
$this->_outputMessage(‘error’); will render the the error view.
This view you have to create in APP/views/errors/xml/set_error.ctp:
<result>
<request><?php echo h($request) ?></request>
<error><?php echo h($message) ?></error>
</result>
But how to call this method when an error has occurred? For instance in your users_controller the view() method is returning the profile of the current user. But if he is not loggedin you get an error. I set some template vars and use the beforeRender() in app_controller to catch errors for ALL actions and controllers and display that error:
// users_controller.php
public function view($nickname = null) {
if (!$this->Auth->user()) {
$this->set(array(
'code' => 401,
'error' => __('You are not authorized to view', true)
));
$this->set('return', false);
}
else {
// find
$this->set('return', true);
}
}
// app_controller.php
public function beforeRender()
{
$varsSet = $this->viewVars;
if (isset($varsSet['return']) && $varsSet['return'] == false) {
$this->cakeError('error', array('code'=>$varsSet['code'],'message'=>$varsSet['error']));
}
}
I’ll check if the viewVar ‘return’ is false and then call the cakeError method. So I can use this application wide for every controller.
And this is how the view looks like after render:
<?xml version="1.0" encoding="UTF-8" ?>
<result>
<request>/users/view/primeminister.xml</request>
<error>You are not authorized to view</error>
</result>
With the Apache headers:
HTTP/1.x 401 Unauthorized Date: Tue, 31 Mar 2009 22:11:38 GMT Server: Apache X-Powered-By: PHP/5.2.6 Content-Length: 1488 Keep-Alive: timeout=15, max=100 Connection: Keep-Alive Content-Type: text/xml; charset=utf-8
Maybe you have some optimizations or suggestions? Leave a comment!
Cheers!
April 7th, 2009 at 02:40
[...] Displaying custom error message with the right HTTP response code [...]
October 19th, 2009 at 18:11
This is awesome, thanks so much for this. Refactored all my error handling to indirectly use the error function above and return header codes. Cheers!