views:

143

answers:

4

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/license/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 [email protected] 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?

A: 

Some people use the autoloader to create exceptions on-the-fly.

Sjoerd
@Sjoerd, ala http://www.php.net/manual/en/function.spl-autoload-register.php#90648?
Inkspeak
+1  A: 

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 greps 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...

ircmaxell
Agreed, `eval` is evil in general. But instead of grepping exception definitions, one may grep for exception calls. This should be fine. Any other cons? BTW, wouldn't be better for the docs: `Exception`, `Exception_Database` `Exception_Database_Connection`? How can I easily list all `Zend_Exception`s I can throw? (PS, those comment boxes su*%s ;)
takeshin
I don't use Zend (I just don't care for it). But I put all exceptions into a package-specific sub-category. So you'd have all the exceptions under the `Database` category/package's `Database_Exceptions` subcategory... I think it's just a tradition having the `Exception` at the end. And yes, you can `grep` for the calls, but I still think having the definition is better (Since seeing `throw new Database_Connection_Exception()` tells you little about the exception, but seeing `class Database_Connection_Exception extends RuntimeException` may tell you more (the database is a trivial example).
ircmaxell
@ircmaxell The other disadvantage of `eval` is not having code completion in IDE…
takeshin
A: 

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.

Yanick Rochon
True, but that's not the point here. As I wrote, we all agree that we need those ghost files. But this is a boring job to create them. As we are programmers, we could write some smart code to do this simple task for us. Can't we?
takeshin
@takeshin: Never forget that there's a significant difference between `can` and `should`. Quoting Michael Crichton, `Yeah, but your scientists were so preoccupied with whether or not they could, they didn't stop to think if they should`... There are some shortcuts that we as developers can take, but there's always a tradeoff... I'm not saying you shouldn't do this, but don't focus on if you **can** create a solution, focus on if you even **should** bother (or if this is along the same lines as micro-optimization)...
ircmaxell
@ircmaxell, True, true. I'm just asking whether we should or not. Your approach described above isn't perfect, but is a lot more useful that the Zend approach (separate exception for each sub package too). The ideal solution would be to have separate namespace with manually defined base exceptions for each package (to tell which standard exceptions they extends) and then, automatically generated exceptions, as you described. However, this solution has some drawbacks too, so I guess the best is to stay what we have now :)
takeshin
@takeshin: see my recent edit of my original answer for some insight on that point...
ircmaxell
+1  A: 

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.

weierophinney