Exceptions A Primer

In most modern object oriented languages, when something bad happens, or if a logic condition occurs that the programmer has decided can't be handled in the normal logic flow, an exception is thrown. Hopefully something in the code is setup to catch the exception. (Beginning to grasp the lingo here, exceptions are baseballs; we throw them, we catch them but we do not get paid millions of dollars per year for doing so.)

PHP, however, is the "exception" to the normal implementation in programming languages. PHP itself does not throw exceptions when things go wrong internally; it defaults to the old "barf bits" method of error handling. However, it does support the concept of exceptions. It gives us the ability to throw and catch them but it will have nothing to do with them. So while exceptions aren't the end-all be-all that they are in other languages, at least we aren't left out in the cold.

So if you made it this far, in your head, you now see a baseball with the word "exception" written on it and different pieces of code tossing it around. Good, we are making process. An exception is actually an object though and it has properties and methods. The PHP manual has a great page describing the native Exception class located at http://www.php.net/manual/en/language.exceptions.php. Exception is a powerful class in that it can carry with it, what went wrong, an error code as well as the file and line number where it was thrown. Most importantly for us as we are building applications, it has a complete stack trace available to show us.

Zend Framework uses Zend_Exception and its children to handle exceptions. Zend_Exception is a subclass of the native Exception. This gives it all the power of Exception but identifies itself as belonging to Zend Framework. (Seriously, that's all the subclass does. It's important but if you look at the code for Zend_Exception, there isn't any!) As we will see below, we can handle different types of exceptions differently. So identifying an exception as a Zend_Exception allows us to handle it separately than a generic Exception or a "MyApplication_Exception".

So let's look at a simple piece of code that uses exceptions and see why they are so cool.

Public function thisBlows() {

throw new Exception('This came from thisBlows()');

echo "This line will never execute";

return;

Now in our main body of code: thisBlows();

The first thing you will notice is that we have a line of code that will never execute. Once an exception is thrown, execution stops in that code block. If that codeblock is a method then control is automatically returned to the calling code block.

If you actually typed all of that in, saved it and executed it, then you have probably noticed that nothing happened. In PHP, since exceptions are not part of the native error handling mechanism, nothing happens if you just throw an exception. Like any good baseball, you have to catch it for something to happen. So let's look at catching them. The mechanism we use to catch exceptions is the Try/Catch construct.

thisBlows();

echo "This is another orphaned line as it will never execute."; } catch (Exception $e) {

echo "we caught an exception!";

Now we have successfully thrown and caught an exception! (Pat yourself on the back.) In this situation, all we did was print something to the screen. In some cases, that's all you need to do, however, the real power with exceptions is that you have the power to clean up any mess and exit gracefully when things go wrong. The easy example has to do with databases. If you use transactions when updating your database you know that there are times that you need to rollback instead of committing. In most cases, the reason you want to rollback is programmatic and not because of database failure or anything. With exceptions you can easily begin a transaction and if something goes wrong, throw an exception. Wrap all the code in a "try/catch" and at the end of the try block, commit the transaction. In the catch, you can rollback the database, notify the user, even log that you didn't update. Yes, that can all be done with a lot of creative work and some IF statements, but exceptions make it so much easier.

Let's look at one final example before we move on to using exceptions in our example. We discussed that we can catch different exceptions and process them separately. This would be the case, if for instance we wanted to handle Zend_Db_Exception separately from Zend_Exception. The code to do that would look something like this:

thisBlows();

echo "This is another orphaned line as it will never execute."; } catch (Zend_Exception $e) {

echo "We caught a ZencLException."; } catch (Exception $e) {

echo "We caught a generic Exception.";

As you can see, we can chain as many catch blocks as we need to handle all the problems we may encounter.

The native Exception class, along with almost all of it's children (unless overridden by a developer) takes two parameters in it's constructor. The first is the message and most of the time, this is really all you need. Something to tell someone what went wrong. The second parameter is an integer error code. If you've created MyApp_Exception and use it in three different places, you may want to be able to make a programmatic decision on how to proceed with the catch. In this case, you have two options. You can either parse the message and look for some key word or use the error code. Of the two, using the error code is much easier.

throw Exception('This came from thisBlows()',1); Now, in our catch block, we can make a further determination as to how to proceed:

thisBlows();

echo "This is another orphaned line as it will never execute."; } catch (MyApp_Exception $e) { if ($e->getCode()==1) {

echo "We caught a MyApp_Exception and it came from thisBlows()."; } else {

echo "We caught a MyApp_Exception.";

Of course this is a very simple example. As your program grows, so will your efforts to deal properly with situations that crop up that you did not anticipate and that's where exceptions shine.

A word of warning about catching exceptions; once you catch one, it's your problem; you have to deal with it. This is why subclassing Exception for your own use is a good idea. By creating MyApp_Exception and only throwing it, you can safely catch MyApp_Exception and handle any problems in your own code leaving Zend_*_Exception and Exception to be handled at a lower level.

If you've been paying attention at all, some of this will be familiar to you already. That's because in our bootstrap we actually have a try/catch construct.

$frontController = Zend_Controller_Front::getInstance(); $frontCont roller->throwExceptions(true);

$frontController->registerPlugin(new Cal_Plugin_MemberInit()); $f rontCont roller->setCont rollerDirectory('c:/web/htdocs/app/controllers/'); $frontController ->setParam('noErrorHandler', true); $frontController->dispatch( ) ; } catch (Exception $exp) { $contentType = 'text/html';

header("Content-Type: $contentType; charset=utf-8"); echo 'an unexpected error occurred.';

echo '<h2>Unexpected Exception: ' . $exp->getMessage() . '</h2><br /><pre>'; echo $exp->getTraceAsString();

At this level, we catch anything that is an Exception or a subclass of Exception; basically, if it hasn't been handled by this point, we deal with it in a very blunt way; we barf bits onto the screen for the user to see. This is not the best thing to do in a production system. A better solution would be to create a log file for the information and save it to your server's disk, or package it up and save it in your database and then gracefully notify the user that an error has occurred. In development, we really want the details of what went wrong but in production we don't want to bother the user with a stack trace or risk exposing sensitive data to the user. Thankfully, Zend Framework is flexible enough to allow us to have our cake and eat it too. By default, Zend_Controller_Plugin_ErrorHandler will attempt to handle some of the errors thrown by Zend Framework itself. Specifically, it will handle missing controller/actions as well as exceptions thrown inside an action. It will not catch exceptions thrown by plugins or within the routing.

If you want to jump ahead and just dive into the code, check out example7.zip.

First things first, we need an ErrorController. It doesn't have to be much of a controller, as a matter of fact, it can be an absolute minimum, defining an empty method called errorAction().

<?php class ErrorController extends Zend_Controller_Action {

public function errorAction() {

} // public function errorAction()

} // class ErrorController extends Zend_Controller_Action

With this in place we could define our error/error.phtml, change a couple of settings in the bootstrap and things would work. However, that really doesn't give us much in the way of information. Sure in the error.phtml we can give the user a nice warm fuzzy feeling by telling them that we know there is an error and are working on it, but where the love for the developer? Let's go a bit deeper in our ErrorController and you'll see that we can make both camps happy.

<?php class ErrorController extends Zend_Controller_Action {

public function errorAction() {

All exceptions are logged in an object that is registered in the request called error_handler. This gives us a handle to the error.

If the problem is that we are missing a controller or an action, let's set a "404" header to send back to the browser along with our message.

case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: case Zend_Cont roller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: $this->getResponse()->setRawHeader('HTTP/1.1 404 Not Found'); break;

default : break;

Next we, hand off the relevant information to the view for display. We do this whether or not we will display it.

$exception = $errors->exception;

$this->view->message = $exception->getMessage(); $this->view->trace = $exception->getTraceAsString();

Finally, we log the error in an error log so the developers can see it.

$log = new Zend_Log(new Zend_Log_Writer_Stream(Globals::getConfig()->dirs->tmp.' Error.log '));

$log->debug($exception->getMessage() . "\n" . $exception->

getTraceAsSt ring()); return;

} // class ErrorController extends Zend_Controller_Action

As you can see it's pretty straightforward; nothing that we've not covered before except for the fact that in the case of an error, this controller/action will be called for us automatically if it's set up. There is one new setting that we need to add to the config.ini, dirs.tmp. This tells the error action where to write the Error.log to. We'll cover that in just a bit.

Now that our controller is in place, let's turn our attention to the view script. to keep with the convention, we need to create view/scripts/error/error.phtml. Here's what it should look like.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/

xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head>

<title><?PHP echo Globals::getConfig()->title;?></title> </head> <body>

<h1>An error occurred</h1> <hr />

<?php if (Globals::getConfig()->debug) { ?> <strong><?php echo $this->message;?></strong><br /> <pre>

<p>We are sorry but the system seems to have hit an error. Have no fear, we have notified our engineers and have a highly qualified team rushing to fix the error. In the mean time, may we suggest a game of Solitaire?</p> <?php } ?> </body> </html>

Notice that after we notify the user that there has been an error, we branch based on an option in our config file. If debug is true then we dump the error message and stack trace that we stored earlier. However, if it's false, we speak softly and reassuringly to the user.

Next, we add to our config.ini file, the two options that we have been using.

dirs.tmp = "c:\web\htdocs\tmp\" debug = 0

Adjust the tmp directory to suit your setup. To see the debug messages, set debug = 1.

Finally, we need to revisit our bootstrap. There is a lot in there now that we just don't need.

Here is our new bootstrap, now that we have offloaded the error handling to the ErrorController. Notice that while we are rearranging things, I took the liberty of getting rid of the variable $frontController and use fluent interfaces to make it all one call.

<?php ini_set('max_execution_time',600); $lib_paths = array();

$lib_paths[] = "c:/web/htdocs/application"; $lib_paths[] = "c:/web/htdocs/lib/";

$inc_path = implode(PATH_SEPARATOR, $lib_paths);

set_include_path($inc_path);

require_once 'config/Globals.php'; require_once 'Zend/Loader.php'; Zend_Loader::registerAutoload();

Zend_Controller_Front::getInstance()

->registerPlugin(new Cal_Plugin_MemberInit())

->setCont rollerDirectory('c:/web/htdocs/application/cont rollers/') ->dispatch();

Now, back to our regularly scheduled example. First, while we could define our own custom exception that subclassed Zend_Exception, we won't. It's enough to knowthat we could. For our simple needs, Zend_Exception will do just fine.

The first thing we need to do is figure out where we want to throw exceptions and where we want to catch them. In reality, we should touch most areas of code that we've written and either throw exceptions on specific cases or catch and process them. Since this is just an example though, I'm only going to touch a couple of areas. The first will be IndexController::extractAction(). This is really the heart of our little application so let's see where exceptions can be used to streamline the processing. Most of this code you have already seen, however since we touch so much of it, I'm going to show you the entire method so you can see where things go and not just what goes there.

public function extractAction()

Since we will be throwing exceptions throughout processing, let's wrap the entire method in a try/catch block. It should be noted here that you can nest try/catch blocks so that if you have code inside a try/catch that you want to handle with its own exceptions, you can. Heed the warning above though, you catch it, you deal with it.

$token = Zend_Filter::get($this->getRequest()->getPost('token'), ' St ripTags');

* Execute the token check */

Here, we throw our first exception. Before this, we set the FlashMessage helper and redirected to the home page. We've moved this code to the catch block since in all cases, that's what we want to do and this way, we only have to code it once. By throwing this exception, we have now stopped execution in the try block and moved to the appropriate catch block.

throw new Zend_Exception("I'm sorry but I think there has been an error.

* Reset the token

$this->generateToken();

* get the URL passed in from the form

$url = Zend_Filter::get($this->getRequest()->getPost('url'), 'StripTags');

Here is another chance to stop the processing and notify the user of an error condition without actually hitting an error. If we can't load the page then Yahoo! can't analyze it. This will either display a fatal notice on the screen or show the user a blank screen, depending on how things are setup in your php.ini. Neither situation is good as the user doesn't have a chance to correct the problem.

If you've played with the sample app at all, you know that without a protocol (http://) it will just blow chow. So let's check it and notify the user if they forgot to put it in.

//throw new Zend_Exception("All URLs must start with http://"); } // if (substr(strtolower($url),0,7)!='http://')

* read page into memory

* requires allow_url_fopen to be true

Again here, we check to make sure that Yahoo! has something to analyze. Because we are lazy and using file_get_contents() to fetch the web page, it is entirely possible that the page did not load. There are too many reasons why this would happen to list here. What is important to know though is that if file_get_contents() fails, it returns false. Therefore, we can test for a value of false and if it exists, throw an Exception. This protects the app from Yahoo! failing on ii and keeps Yahoo! from getting mad at us because we keep asking them to analyze "false".

throw new Zend_Exception("There was an error loading the url. ",$url);

* strip out everything but the content

* Many thanks to #phpc members ds3 and SlashLife for the RegEx

preg_match('/<body[~>]*>(.*?)<\/body\s*/isx', $page, $matches);

* Filter out the cruft

$content = preg_replace('/(<style[~>]*>[~>]*<\/style\s*>)/isx', '', $content);

$content = preg_replace('/(<script[~>]*>[~>]*<\/script\s*>)/isx', '', $content);

$content = preg_replace('/(&.*?;)/isx', '', $content);

$content = Zend_Filter::get($content, 'StripTags');

* send it off to Yahoo for analysis

$client = new Zend_Rest_Client('http://search.yahooapis.com/

ContentAnalysisService/V1/termExt raction'); $client->appid('rqP7px3V34H2DVtFJq04ZFTHfiRJQmlvnQze4T313MFEGLAy1. lw8PZBWeRqk40R_30-') ->context($content) ->output('xml'); $result = $client->post(); $client = null;

Zend_Rest_Client has an isError() method but in this particular situation, it does not report error conditions properly. Therefore, we check to see if Yahoo! sent us a message back in the payload. They only do that if there was a problem processing the request. If they do return us an error, we hand it off to the exception to display to the user. Now, this isn't the brightest thing we can do here. Most users won't know that "invalid content" means that for some reason, we handed it content that it couldn't process and even if they do, most aren't going to know what to do about it. In a production application, you would want to catch this problem and see if there was a way you could deal with it or at least try and give the user a bit more help in solving the problem. However, since this is a demo application and you are the only user I'm designing for, I'll just hand it back to you and let you figure it out on your own.

throw new Zend_Exception((string)$result->Message);

* Hand everything off to the view for output */

* Build our keywords list */

$keywords=array();

foreach($result->Result as $item) {

$image = $wp->fetchImageTag((string)$item); $this->view->result[] = array((string)$item,

$image); $keywords[] = (string)$item; } // foreach($result->Result as $item)

$wp->addAnalysis(date('Y-m-d h:i:s',mktime()),$keywords);

Now let's deal with those exceptions we've been throwing. Again, in more complex applications, we may want to clean things up, destroy resources, etc. before handing it back to the user. However, in this case, we are just going to notify the user that things have gone awry and we need a little help from them before trying again. Since the only way to get to this action is to come from the indexAction(), after we notify the user, we throw it back to that page so it can be displayed.

$this->_helper->flashMessenger->addMessage("There was an error processing you r request.");

$this->_helper->flashMessenger->addMessage("Exception Type:".get_class($e) );

$this->_helper->flashMessenger->addMessage("Exception Message:".$e-> getMessage());

return false;

} // public function extractAction()

That's one way to use exceptions, throwing them and catching them in the same code block. It's a valid strategy for handing issue that may arise. However, exceptions are much more powerful than just that. As we saw in our initial discussion of exceptions (those of you who skipped it may want to scan back a few pages and look at the sample code), exceptions are useful for stopping processing in methods. One way we can use this is to prevent the instantiation of classes that we only want used as static. {{include Globals.php}}Globals.php is a good example of that. We never want to instantiate a copy of Globals. All methods are static, all properties are protected. There's just no reason to do it. However, there's nothing currently preventing someone from executing:

$myGlobals = new Globals();

One way we can prevent this is to throw an exception. Let's add a constructor to Globals.php.

public function __construct()

throw new Zend_Exception('You cannot instantiate Globals.php! It is static only.');

There, now it cannot be instantiated. If we try, well, it won't be pretty. Here, let's give it a go in ourIndexController::indexAction:

public function indexAction() {

$this->view->token = $this->generateToken(); $flash = $this->_helper->getHelper('flashMessenger'); if ($flash->hasMessages()) {

$this->view->message = implode("<br />", $flash->getMessages()); } // if ($flash->hasMessages())

} // public function indexAction()

Save that and execute it. You should see something like what Figure 10.1 shows.

Make sure you comment that line out or remove it before you go on. Otherwise, nothing is going to work.

Figure 10.1

Was this article helpful?

0 0

Post a comment