« Exceptional PHP: Nesting Exceptions In PHP | Exceptional PHP: Introduction to Exceptions » |
On Monday, we talked about the basics of exceptions and how they are used in PHP (as well as in other object-oriented programming languages). As promised, today we are going to talk about extending the base exception class in PHP.
One of the things that you can (and should) do with PHP exceptions is extend them to suit your own purposes. While the base Exception class in PHP is neither abstract nor impossible to use on its own, extending exceptions give you a great amount of flexibility and power, particularly in three areas: customization, identification, and abstraction.
First, a little bit on how to expand and extend exceptions. Exceptions are extended just like any other class using the extends keyword. There aren’t a whole lot of methods we can override in the base Exception class, as most are defined as final; however, this does not prohibit us from adding our own methods. For more on the Exception API, please check out the details in the manual.
Extending Exceptions For Customization
When you extend the base exception, you can customize the exception to suit your needs. For example, I always include a setUserMessage() and getUserMessage() in my exceptions, so that when the view gets the exception, it can display a nicely formatted user-friendly error message without a bunch of junk like the stack trace.
class MyException extends Exception { protected $userMessage; public function setUserMessage($message) { $this->userMessage = $message; } public function getUserMessage() { return $this->userMessage; } }
In the example above, we have successfully extended the Exception class and added our own methods. This adds a level of custom code to the exception. We can change a number of performance behaviors as well (we could theoretically add a __destruct() method that logs the exception to a custom error log, for example).
Extending Exceptions for Identification
When a generic exception is thrown in your application, you have to read through the stack trace to determine where and what caused that exception to be thrown. This can be a time-consuming process, especially if your stack trace is exceptionally large (pun intended). Instead of doing this, we can extend the Exception base class and name our new exceptions things that are easy to identify.
class ControllerException extends Exception {} class ActionException extends Exception {} class ModelException extends Exception {} class ViewException extends Exception {}
By doing what we’ve done above, we’ve now made it easy to identify from which part of our application the exception comes from. If you have an exception raised that is of type ModelException, you know that the exception came out of the model, and you can ignore the view or the controller.
It’s perfectly acceptable to extend exceptions and not add methods to the extended exception. You can opt to extend the Exception base class into another base class, where you define a variety of custom methods, and then extend from that, like so:
class MyException extends Exception { protected $userMessage; public function setUserMessage($message) { $this->userMessage = $message; } public function getUserMessage() { return $this->userMessage; } } class ControllerException extends MyException {} class ActionException extends MyException {} class ModelException extends MyException {} class ViewException extends MyException {}
In this case, each of our new exceptions will have a setUserMessage() and getUserMessage() method.
Extending Exceptions for Abstraction
One of the most important principles in object-oriented programming is abstraction, and specifically, layer abstraction. Layer abstraction is the abstraction of each layer from the other layers, like an onion. Each layer should have its own exception type.
For example, a request that goes into the Controller might get sent to the Model. If an exception is raised in the Model, it should not be output in the View. It should instead be captured in the Controller and handled, or a new Controller-type exception should be thrown that wraps the exception raised in the Model.
The same applies for libraries. If your Twitter or Database library raises an exception, any class making use of those libraries should trap and handle that exception, even if it means throwing an exception of its own.
If we have a bunch of generic Exception types running around, not only do we not know where the Exception was raised, but it is impossible for us to ensure that we have properly abstracted our application. By defining new exceptions, we know that a ModelException came from the Model, and therefore can handle it appropriately.
Summary
Writing your own exceptions makes your application much more powerful and much easier to use in other situations and other applications. We have yet to talk about how to take one exception and incorporate it into another (nesting exceptions), which is something we will discuss in a special Thursday article tomorrow.
Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP
Posted on 11/11/2009 at 1:00 am
Carlos Aguado wrote at 11/11/2009 5:50 am:
Hi Brandon,
Thanks for sharing, good article!
When it comes to logging an exception, I’ve come to find useful to do it in such a way that an unique global application “exception code” is attached to the exception info. Something like this in your app:
try {
// Do something that can throw a Zend_Xxx_Exception
}
catch (Zend_Xxx_Exception $e) {
BasicClass::logException(3001, $e);
}Then you log it this way in your BasicClass::logException()
method:public static function logException($exceptionCode = 0, $exception)
{// Do custom exception formatting stuff…
$exceptionInfo = get_class($exception).’ Code ‘ . (int) $exceptionCode . ‘; Msg: “‘ . $exception->getMessage() . ‘”‘;
$zfLogger->log($exceptionInfo, Zend_Log::ERR, $exception->getFile(), $exception->getLine());
}
}This global application exception code approach is used in some big companies and is really useful for later troubleshooting.
Jon wrote at 11/11/2009 9:40 am:
I’m not sure I agree with some of the things in this article. Namely defining a var for the class named $userMessage, then defining a function that all it does it set the message, and then a another function that just calls the variable.
It’s unecessary overhead. If you want to go this route, why not just make the variable public and define it whenever you want, wherever you want. Defining a function to sit in memory and wait to be called is not exactly the best idea..
Better though than the above would be just to define $userMessage as an Array(), catching whatever messages (exceptions, whatever), and then using that array to display messages. I would put it in the destruct() myself and have it report however I wanted.
Also, you said you were extending the Exceptions class to customize it, but you didn’t do any customization at all to the Exceptions class. Finally, the “final” keyword isn’t suposed to be the same as public/private/protected. Not to mention that if the Exceptions class was defined as “final”, trying to extend isn’t theoretically suposed to work.
I think you may want to reconsider a lot of the above article as it may be misleading to many…
Dave wrote at 11/11/2009 12:26 pm:
I’m not 100% sure you’ve actually explained *why* it’s a good idea that each layer has its own exception type.
You seem to be saying by not handling and rethrowing you *don’t* know where where the exception was raised. Surely the opposite is true? If you don’t handle and rethrow you know *exactly* where the original exception was raised without it being dressed up in layers of other exceptions that were triggered as the problem bubbles up through the layers.
You’ve also said “it is impossible for us to ensure that we have properly abstracted our application” which I’m not sure I understand.
Brandon Savage (@brandonsavage) wrote at 11/14/2009 8:17 am:
Dave, I hope I responded to your concern with the third blog post in the series about nesting exceptions.
Jon, I think you completely missed the point of this article. Most exceptions (look at Zend Framework) don’t actually implement new functionality; they simply extend the existing exceptions so that you know which library threw the exception.
As for the getter and setter method, this is a tried-and-true programming strategy, and though you may disagree with it, that’s your choice. Lots of people choose to use it, and I’m one of those people; it’s not an inappropriate or incorrect way of doing things, just a different one from perhaps how you would do it.
Michael Kimsal (@jsmag) wrote at 11/18/2009 9:21 pm:
re: getters and setters, it’s something that can be hidden in other languages, but can’t be in PHP, and probably won’t be any time soon (or perhaps never).
What would be truly useful is to have generic get/set methods automatically created at compilation time, and any references to retrieving or setting values would always look for the appropriate getter or setter. Since they’d be automatically added at compile time, they’d always be there. For the times that you’d want to modify the behaviour, simply write a getter or setter with the customer behaviour and it would override (or obviate?) the default one that the PHP compiler would normally generate.
This would allow for much more succint code to be written – loads of boilerplate code would be avoided, and there’d be no need to have to refactor calls to properties to explicity use get/set methods – it’d be transparent. Less code, standardized behaviour, what’s not to love? (yeah yeah, some extra work at runtime – code caching would solve the bulk of that issue).
« Exceptional PHP: Nesting Exceptions In PHP | Exceptional PHP: Introduction to Exceptions » |