Exceptional Exceptions

Last updated 136 days ago by Toon Daelman

php

You've made it to this post thinking "Why do we still need to talk about Exceptions?". Well, they're used everywhere in OOP codebases, but sometimes they're used in a way that make debugging a bit difficult. Let's look at some ways to make debugging exceptions a bit more fun!

You know that feeling when you're trying to investigate an Exception that was thrown, but you can't seem to find the origin of it? You dig a bit deeper and you find that it's an exception that was caught and not rethrown, e.g.:

```php try { $this->doSomeImportantStuffWith($foo); } catch (VeryDescriptiveException $e) { // do some stuff here

throw new SomethingWentWrongException('oh no!');

} ```

Now, when you encounter this SomethingWentWrongException, you'll see that the trace takes you back to the 6th line of this code example. All information that was inside the VeryDescriptiveException, including its message, stack trace and other useful information is gone. Of course, debugging that error in doSomeImportantStuffWith() would be much easier if you had all that info.

```php Fatal error: Uncaught SomethingWentWrongException: oh no! in test.php:6 Stack trace:

0 /Users/toon/Projects/devblog/test.php(34): Test->withoutPrevious()

1 {main}

thrown in /Users/toon/Projects/devblog/test.php on line 6 ```

Prevent information loss by using $previous

The obvious answer to this simplified example would be to just rethrow the VeryDescriptiveException instead of throwing a more general SomethingWentWrongException... And that would be valid, but let's say we're implementing an Interface that prescribes that we only throw SomethingWentWrongExceptions. We can't let the VeryDescriptiveException through or we'll break the Liskov Substitution Principle. We want to throw that specific SomethingWentWrongException, while still somehow preserving the information of that previous exception that we caught. Let's check the docs:

PHP Docs for the Exception Constructor

That Throwable $previous = null is what we're looking for! I've almost never seen this being used in the wild, but it's great for our usecase:

```php try { $this->doSomeImportantStuffWith($foo); } catch (VeryDescriptiveException $e) { // do some stuff here

throw new SomethingWentWrongException('oh no!', 0, $e);

} ```

this results in this error:

```php Fatal error: Uncaught VeryDescriptiveException: hello there! in test.php:15 Stack trace:

0 /Users/toon/Projects/devblog/test.php(23): Test->doSomeImportantStuffWith('test')

1 /Users/toon/Projects/devblog/test.php(34): Test->withPrevious()

2 {main}

Next SomethingWentWrongException: oh no! in test.php:6 Stack trace:

0 /Users/toon/Projects/devblog/test.php(34): Test->withPrevious()

1 {main}

thrown in /Users/toon/Projects/devblog/test.php on line 6 ```

As you can see, the stack trace of the original exception along with the one we wrapped it in are presented to us on error! We can see the message as well, and we can even add our own properties to the exception and be presented with them here if we just implement the __toString() method of the exception.

Custom properties in exceptions

Let's say we've been building an API client that does some HTTP requests to an endpoint. Something can go wrong during the HTTP request, and we want to thrown an ApiConnectionFailed exception whenever the HTTP response code is not 2xx or something else fails, so that the rest of our application has one single Exception it needs to prepare for. It would be very handy to debug if that exception contained our HTTP Request & Response objects, e.g.:

```php final class ApiConnectionFailed extends Exception { private $request; private $response;

public static function withHttpRequestAndResponse(
    $message,
    Request $request = null,
    Response $response = null,
    Throwable $previous = null
) {
    $exception = new static($message, 0, $previous);
    $exception->request = $request;
    $exception->response = $response;

    return $exception;
}

public function __toString()
{
    $string = parent::__toString();
    $string .= "\n{$this->request}";
    $string .= "\n{$this->response}";

    return $string;
}

} ```

As you can see, we used a named constructor withHttpRequestAndResponse to be able to keep the default constructor for Exceptions, but to also be able to construct it using a lot of extra relevant debugging information. This means that this Exception behaves like other exceptions and is very transparent to other developers.

Read full Article