2

I'm encountering a tricky problem with Inheritance and the hierarchy of Exceptions offered by the Standard PHP Library (SPL).

I'm currently building a helper library in PHP for REST-based APIs. These APIs can return their own error messages in the form of JSON objects, and these objects include information beyond the properties offered by a PHP Exception. Here's a quick example:

{"error":{"time":"2011-11-11T16:11:56.230-05:00","message":"error message","internalCode":10}}

Occasionally, "message" includes internal structure that could benefit from additional parsing. I like the idea of throwing a particular subclass of Exception, like so:

$error = $json->error;
throw new UnexpectedValueException($error->message, $error-internalCode);

Which later can be selectively caught:

catch (UnexpectedValueException $e)
{
  ...
}

And now we arrive at my dilemma: I'd like to extend the SPL Exception objects so that they can have a "time" attribute, and also perform the extra parsing of "message." However, I'd like to extend them at their level as opposed to creating an extension of the base Exception class, so that the ability to selectively catch exceptions is preserved. Lastly, I'd like to avoid creating thirteen different child classes (the number of exception types defined in the SPL), if at all possible.

Ideally, I could begin with a parent customException object:

class customException
{
  public $time;
  public $message;
  public $internalCode;

  public function __construct($time, $message, $internalCode)
  {
    $this->time = $time;
    $this->message = $message;
    $this->internalCode = $internalCode;
  }

  public function parseMessage()
  {
    // Do some parsing of message
    return $parsedMessage;
  }
}

Then, I'd have a Factory Class that would be able to be invoked like so:

class ExceptionFactory
{
  static public function createException(Exception $e, $exceptionParent)
  {
    $json = json_decode($e->message);
    return new customException($json->time, $json->message, $json->internalCode) extends $exceptionParent;  // Won't work, but hopefully you get the idea
  }
}

After reading php dynamic class inheritance, I can probably get there by using eval(), but that just feels wrong to me. If I have to write the thirteen child classes, then I'll find myself wanting to use multiple inheritance for the desired parent class $exceptionParent and customException. How would you recommend I solve this dilemma? Thank you in advance for your ideas!

Community
  • 1
  • 1
Beowulf
  • 41
  • 5

2 Answers2

1

Having something like:

class MyException extends \Exception {
  const EXCEPTION_TYPE_FOO = 1;
  const EXCEPTION_TYPE_BAR = 2;
  const EXCEPTION_TYPE_JSON_MESSAGE = 3;

  $protected $_data = array();
  $protected $_exceptionType = null;

  public function __construct( $type = null ) {
    if( null !== $type ) 
      $this->_exceptionType = $type;
  }

  public function __get( $name ) { 
    if( isset($this->_data[$name]) ) {
      if( $name == 'message' ) {
        switch( $this->_exceptionType ) {
           case MyException::EXCEPTION_TYPE_JSON_MESSAGE:
             return json_decode($this->_data[$name]);
           // other exception types
           default:
              return $this->_data[$name];
        }
      } 
      return $this->_data[$name];
    }

    return null;
  }

  public function __set( $name, $value ) {
    $this->_data[$name] = $value;
  }
}

So now you could have:

$e = new MyException(MyException::EXCEPTION_TYPE_JSON_MESSAGE);
$e->time = time();
$e->code = '404';
$e->message = json_encode(array('testing'));

And when you catch it

catch( MyException $e ) {
  print_r( gettype($e->message) );
}

Should return array.

I haven't tested the code, I just wrote it but you get the idea.

mobius
  • 5,104
  • 2
  • 28
  • 41
  • Thanks, mobius. That's a clever solution! And I think you've hit upon a key feature: the exception type that has been caught. In your example, you are catching `MyException`, and then checking for type. I'm starting to suspect that I'm going to have to write the 13 classes so that I can specifically catch each one when I want it. Otherwise, someone else's code that performs `catch (UnexpectedValueException $e)` is going to catch my custom Exception, too. And I apologize for multiple edits of this comment: I'm new, here! – Beowulf Nov 13 '11 at 19:14
  • Personally to be honest, I would probably write 13 exceptions. It's much cleaner I guess. The only way to catch MyException without explicitly defining it would be to catch( Exception $e ) which is the parent of all exceptions. The case you are describing would only exist if you were to extend MyException from UnexpectedValueException – mobius Nov 13 '11 at 19:37
  • I think you're right. I went for a run after posting my question, and I think there's a potential for misunderstanding if I did this: `catch (UnexpectedValueException $e) { print_r($e->time); }`. Another developer wouldn't understand where `time` came from. It is cleaner to write the child classes, and that's a Good Thing. It also means I can document my intentions through phpdoc, which is even better. Thanks for the insight, mobius! – Beowulf Nov 13 '11 at 19:45
0

One common solution is to use "marker interfaces" to indicate "their level"

interface MyExceptionLevel extends ParentExceptionLevel {}

class MyException extends Exception implements MyExceptionLevel{}

try {
  // code
} catch (MyException $e) {}

// or

try {
  // code
} catch (MyExceptionLevel $e) {}

I recommend not to use too much magic, especially in such a sensible point like error/exception handling.

KingCrunch
  • 128,817
  • 21
  • 151
  • 173
  • Thanks for the advice on "magic," KingCrunch. It's very possible that I'm over-thinking this problem. I'm starting to lean towards writing the 13 custom classes, each extending a node of the [SPL Exception hierarchy](http://id.php.net/manual/en/spl.exceptions.php). Since `parseMessage()` is going to be standard, I could create a `parentException` class with `public static function parseMessage($message)`. That way each of the 13 child classes could do something like this: `public function parseMessage($message) { return parentException::parseMessage($message); }` – Beowulf Nov 13 '11 at 19:29