Chapter 7. Handling Application Errors Gracefully

7.1. Introduction

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.

7.2. The ErrorController and Error View

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.

7.3. Well, That Didn't Work...

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.php"
bootstrap.class = "ZFExt_Bootstrap"
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
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] Important

Do not forget to revert to the original setting of 1 when finished with this chapter!

Now, let's try that URL again...

7.4. Not All Errors Are Equal

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.

7.5. Conclusion

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.

Powered by jQuery Powered by Zend Framework Powered by Nginx Http Server