Exceptional PHP: Nesting Exceptions In PHP

« »

In the last two entries we have talked about the concept of layer abstraction: that is, that exceptions should not be allowed to pass out of one layer and into another. So, when an exception is raised in the database layer it should be caught in the controller. But how do we go about making sure that exceptions raised in the database layer are properly recorded and processed, ensuring that we have error logging and don’t simply silence our exceptions?

There are a number of ways to encapsulate one exception within another, or “nest” our exceptions. Let’s discuss the ways.

Nesting Exception Messages
The base Exception class has a built-in __toString() magic method, meaning that we can automatically convert our exceptions into strings. This makes the following possible:


try {
    throw new Exception('Message 1');
} catch (Exception $e) {
    $e = new Exception('Message 2: ' . $e);
} 

try {
	throw $e;
} catch (Exception $e) {
    $e = new Exception('Message 3: ' . $e);
} 

try {
	throw $e;
} catch (Exception $e) {
    throw new Exception('Message 4: ' . $e);
}

We get back the following exception error message:

Exception: Message 4: exception ‘Exception’ with message ‘Message 3: exception ‘Exception’ with message ‘Message 2: exception ‘Exception’ with message ‘Message 1’

The messages are nested, but this does generate some potential issues. First, our exceptions do not have stack traces; the logged stack trace will be the stack trace given in the final exception. Second, the exception message doesn’t contain the exception message codes. However, for simple nested exceptions, this is a good strategy.

Extending Base Exception And Writing Nesting Code
It is possible in PHP to nest exceptions by writing code to do so. For example:

<?php

class MyException extends Exception
{
	protected $priorException;

	public function __construct($message, $code = 0, Exception $previous = null)
	{
		$this->priorException = $previous;
		
		parent::__construct($message, $code);
	}
	
	public function getPrior()
	{
		return $this->priorException;
	}
}

This exception will take a third optional argument of a previous exception, allowing you to nest the exceptions. When preparing to log your exception, you can opt to iterate through any possible previously thrown and nested exceptions, and log any of the data you need.

Using PHP 5.3’s Built-In Nested Exceptions
Anyone that noticed the kludgy way I named the $priorException variable probably wondered why; the reason is that PHP 5.3 introduces nested exceptions as a default part of the PHP base Exception class. While the above code will work, if you are utilizing PHP 5.3, you can pass any previous exception as a third argument (like above), and use the Exception::getPrevious() method to get a previously raised exception.

It would still be wise to develop code that would iterate through the exceptions and log the various data you need; PHP doesn’t incorporate a way to automatically do this.

Summary
You can honor layer abstraction and still ensure that the errors raised are logged and handled appropriately using the various exception nesting techniques above. Ultimately, as PHP improves, so will the nesting options in exceptions.

Brandon Savage is the author of Mastering Object Oriented PHP and Practical Design Patterns in PHP

Posted on 11/12/2009 at 1:00 am
Categories: Technology, Best Practices, PHP 5, Object-Oriented Development
Tags: , ,

Chris D wrote at 11/13/2009 3:44 pm:

I just wanted you to know how glad I am to see someone in the PHP-osphere is still creating blog posts that teach something about the language. When I first started with PHP about 5 years ago, Planet PHP was a good source for learning about the language.

Anymore, it’s just a clearing house for for conference announcements

« »

Copyright © 2023 by Brandon Savage. All rights reserved.