Subclassing exceptions
We all agree that using different exception types for different tasks is the way to go.
But then, we end up with creating ghost files like this:
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/lice开发者_Python百科nse/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @category Zend
* @package Zend_Dojo
* @subpackage View
* @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Exception.php 20096 2010-01-06 02:05:09Z bkarwin $
*/
/**
* @see Zend_Dojo_Exception
*/
require_once 'Zend/Dojo/Exception.php';
/**
* @category Zend
* @package Zend_Dojo
* @subpackage View
* @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Zend_Dojo_View_Exception extends Zend_Dojo_Exception
{
}
Then the same for Zend_Dojo_Exception
and the same for Zend_Exception
…
Is there any generic approach for this problem?
Something like throw new \My\Just\Declared\Exception\ (which extends \My\Just\Exception)
, so I didn't have to crate and require all those ghost files?
You seem focussed on the idea of these being "ghost" classes -- classes with no implementation, or marker interfaces. Frankly, you're missing the point.
In ZF1, the Exception classes are component level only, and all exceptions at that level receive the same exception class. This really only allows the following types of catches:
- Global level (catch "Exception")
- Component level (catch component-level exception)
This is only slightly better than simply throwing "Exception" everywhere; you need to carefully examine the exception messages to get an idea of what went wrong.
Now, go and read the proposal carefully.
The point of the proposal is to allow additional levels of granularity in catching exceptions:
- Don't care what exception it is? Catch \Exception.
- Looking for exceptions from a specific component, but don't care about the specifics beyond that? Catch that component's Exception interface.
- Want to look for specific types of SPL exception? Catch on those (in ZF2, exception classes implement the component exception interface, and extend appropriate SPL exceptions).
- Want to catch a specific exception type within the component? Just catch it.
Basically, we allow for a greater amount of granularity just on the exception TYPE now; you would only need to check the message if there are multiple exceptions of the same type that could be thrown by the operation you're trying. Typically, this shouldn't be the case.
The SPL exceptions are fairly rich semantically, and many exceptions within ZF would be better categorized as these types (e.g., invalid arguments should raise an InvalidArgumentException; inability to resolve a plugin would be a good RuntimeException; etc.). With ZF1, this isn't possible -- we have to inherit from the component level exception, period. By moving to a marker interface, we get both the ability to catch component-level exceptions as well as the SPL-level -- and also on a more specific exception type.
In good practice, not really... There are however some hacks you could do if you REALLY wanted to do this, but I still think they are more evil.
For example, one of those hacks is to eval
those classes into existance via an autoloader. This is bad because if someone ever grep
s for the exception's definition or for the exceptions that your package throws they are going to be a whole lot of nada in return...
public static function load($name) {
$parts = explode('_', $name);
if (strtolower(end($parts)) == 'exception') {
//make it extend the proper exception
array_pop($parts); //get rid of the last Exception bit
array_pop($parts);
$parts[] = 'Exception';
$parent = implode('_', $parts);
$code = 'class '.$name.' extends '.$parent . '{}';
eval($code);
}
}
But again, let me stress this is usually a bad idea.
Personally, I inherit from multiple "base exceptions" (typically the SPL
exceptions). So for example a Database_Connection_Exception
might extend RuntimeException
, trying to commit a non-open transaction might throw a Database_Not_In_Transaction_Exception
which might extend LogicException
. The point being that declaring them separately lets you do more than just straight heiarchal inheritance (not to mention is better for documentation, since people can look at a glance at the defined exceptions, and you can actually override methods to better suit your needs if appropriate)...
Edit: Based upon your mention of Zend
's tendency to do one exception per sub-package, here's how I do it...
Basically, I have a few "global" exceptions that are used throughout the application(s). These include (but are not limited to): ClassNotFoundException
, FileNotFoundException
, NotCallableException
and a bunch of others. Basically just those that aren't package specific, but need to convey more meaning than the core PHP exceptions can...
Then, I declare exceptions on a package level only. In that directory (package/exceptions
) I declare each and every exception as necessary. So one subpackage may have 5 or 10 exceptions to distinguish different conditions, while another subpackage (within the same package) may have none. So I declare them as needed so that the exception means what happened.
I do this for a simple reason. I don't care about where
the exception was thrown from (And if I really did, I can inspect the backtrace that's automatically generated inside of the exception). I care about why
the exception was thrown. And that lets me properly handle the exception...
Some people use the autoloader to create exceptions on-the-fly.
From my point of view, exceptions are or should only be visible in development mode. Production mode should not display exceptions to the user/client, but a simple page showing that "An problem was encountered" or something similar. Those exceptions should be logged at best. The bottom line is that they are mostly useful for debugging or when necessary to control the execution flow (when they are expected). You could simply throw Zend_Exception
all the way, but that would only make your exception too generic and would be difficult to find the cause in try..catch
I usually create ghost files -- as you call them -- mostly on the "package" level or when a method is expected to throw an exception or another (so I can catch that very particular exception and treat it consequently).
You may end up with many ghost files but it will only make your project more organized and easy to debug in the end. And since those exceptions are only loaded in a need-to basis, having lots of them doesn't impact on performance.
In fact PHP already have exceptions subclasses, see here for the complete list. Therefore the documentation on their usage is quite limited, you can check this post on php exceptions to get the meaning of each one.
精彩评论