Table of Contents
Our simple little application is growing up! To finish off this section of the book, designed to familiarise you with the Zend Framework and its basic nuts and bolts when setting up applications, let's take a look at error handling.
As we configured in the
last chapter, Zend_Controller_Front
will only throw
exceptions when in development or testing mode. In production all errors
and exceptions should not be displayed to users. However, when something
does go wrong, we can't just leave a user with a blank browser screen and
a bad case of bemusement steadily degrading into irritation. We should
instead inform the user that a problem does exist, that we're extremely
sorry, will torture the developer responsible as soon as possible and
hopefully resolve this little glitch as soon as the torture session has
had its desired effect.
To accomplish this,
Zend_Controller_Front
assumes that we will create a
new Controller, ErrorController to be exact, to handle these situations.
Failure to create an ErrorController
will result in
our application throwing a fit about it being missed, especially when the
error was from a URL that points to a non existing location that should
return a 404.
This handling of
Exceptions is performed by a Front Controller plugin called
Zend_Controller_Plugin_ErrorHandler
.
We'll keep our error reporting to the user as simple as possible with this minimal class.
<?php
class ErrorController extends Zend_Controller_Action
{
public function errorAction()
{
}
}
As we covered
previously, all Controller actions will automatically render a related
view. Since we have an errorAction()
method on an
ErrorController
, this means a view script at
/application/views/scripts/error/error.phtml
will be
rendered. If it exists - so add it now!
<?php echo $this->doctype() ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<?php echo $this->headMeta() ?>
<meta name="language" content="en" />
<title>Oops! There's Been A Problem!</title>
</head>
<body>
<h1>An Error Has Occurred</h1>
<p>We're truly sorry, but we cannot complete your request at this
time.</p>
<p>If it's any consolation, we have scheduled a new appointment
for our development team leader in Torture Chamber #7 to encourage
his colleagues to investigate this incident.</p>
</body>
</html>
Ah, if only it were so easy...
Head back to the browser
and attempt to call http://helloworld.tld/route/does/not/exist
.
Because this URL matches no route (and therefore no Controller) it should
trigger this error message.
Instead of our error page, we instead got an actual Exception message and its details sent to the browser.
The
ErrorController
was designed to trigger only if the
Front Controller is told never to throw an Exception - except for
Exceptions you have to throw because someone forgot to add
ErrorController
! Actually, the infamously
irritating Exception about not finding an ErrorController comes from this
situation. To test the ErrorController
we need to
make a temporary edit to /config/application.ini
to
disable the throwing of Exceptions from
Zend_Controller_Front
by changing the very last
line "resources.frontController.throwExceptions" to equal zero (i.e.
false). This ensures the necessary ErrorHandler plugin is allowed to serve
an Exception free error page as we have created.
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 bootstrap.path = APPLICATION_ROOT "/library/ZFExt/Bootstrap.html" bootstrap.class = "ZFExt_Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers/index.html" resources.view.encoding = "UTF-8" resources.view.doctype = "XHTML1_STRICT" resources.view.contentType = "text/html;charset=utf-8" resources.modifiedFrontController.contentType = "text/html;charset=utf-8" [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 resources.frontController.throwExceptions = 0
Important | |
---|---|
Do not forget to revert to the original setting of 1 when finished with this chapter! |
Now, let's try that URL again...
You might think we can stop here and quietly escape Torture Chamber #7, but you would be wrong. If you look under the hood, our error is extremely vague and, worse, we're still responding to HTTP requests with a HTTP/1.1 200 OK header. Now call me stupid, but didn't we just admit to a failure by daring to give a user an error message? If our application experiences an error it should never, ever return such a status code and message.
While this may not seem very important, it is. Not everything in our application may be served to Joe Bloggs through a browser. Some requests may come from other applications which may rely on the HTTP response code rather than your blatant cruelty to developers to detect any errors. In any case, you must contend with your own internal reviewing of applications. Will you be able to track problems when every single request is an apparent success according to your web server logs? Then there is also the great problem called RFC 2616 "Hypertext Transfer Protocol -- HTTP/1.1" which explains the 2xx range of status codes with "This class of status code indicates that the client's request was successfully received, understood, and accepted". In other words, the W3C have a special souped up room called Torture Chamber From Hell #1 waiting for those whose applications claim to be omnipotent.
A failed routing is not
a success, it means that either our routing has broken down or, far more
likely, the user requested using an invalid URL. Since using the wrong URL
is the user's fault, we probably shouldn't send our Team Leader to the
torture chamber just yet. Instead we should explain to the user that they
are wrong (politely) and include a 404 Not Found status in case the user
is a machine. To accomplish this, we need to do some checking in our
ErrorController
.
<?php
class ErrorController extends Zend_Controller_Action
{
public function errorAction()
{
$error = $this->_getParam('error_handler');
switch ($error->type)
{
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
$this->getResponse()->setHttpResponseCode(404);
$this->view->statusCode = 404;
break;
default:
$this->getResponse()->setHttpResponseCode(500);
$this->view->statusCode = 500;
}
}
}
Above, we grab the ErrorHandler plugin and retrieve its error type value. If the error corresponds to a routing problem we'll set the HTTP status code to 404 reflecting that this is not our fault and the path simply doesn't exist for our application. Otherwise, we set a HTTP status code of 500 for all other errors. The status code 500 is accompanied by the message "Internal Server Error" in the header. Let's now update our existing error template.
<?php echo $this->doctype() ?>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<?php echo $this->headMeta() ?>
<meta name="language" content="en" />
<title>Oops! There's Been A Problem!</title>
</head>
<body>
<h1>An Error Has Occurred</h1>
<?php if ($this->statusCode == 500): ?>
<p>We're truly sorry, but we cannot complete your request at this
time.</p>
<p>If it's any consolation, we have scheduled a new appointment
for our development team leader in Torture Chamber #7 to encourage
his colleagues to investigate this incident.</p>
<?php else: ?>
<p>We're truly sorry, but the page you are trying to locate
does not exist.</p>
<p>Please double check the requested URL or consult your local
Optometrist.</p>
<?php endif; ?>
</body>
</html>
Obviously, user retention is not a primary goal of this chapter.
If you check with a browser using any bad URL, you should get the relevant message along with a 404 status code in the headers. If you do something to throw a different Exception, you should receive the status code 500 response and related HTML message.
Handling errors is not a
difficult task but it is a necessary one, especially when our applications
may be visited by other application driven clients which rely on the
communication of appropriate status codes and headers, and not human
readable HTML pages, to assert whether a HTTP request was a success or
otherwise. It is strongly recommended that you ensure your application
always sends appropriate headers and codes. While the
ErrorController
seems fairly simple, it's easy to
see that we could expand it to log errors or even send out email
notifications.