Creating Exception types on-the-fly in modern PHP
We pioneered a pattern for exception handling for Zend Framework back as we initially began development on version 2 around seven years ago. The pattern looks like this:
- We would create a marker
ExceptionInterface
for each package. - We would extend SPL exceptions and implement the package marker interface when doing so.
What this gave users was the ability to catch in three ways:
- They could catch the most specific exception type by class name.
- They could catch all package-level exceptions using the marker interface.
- The could catch general exceptions using the associated SPL type.
So, as an example:
try {
$do->something();
} catch (MostSpecificException $e) {
} catch (PackageLevelExceptionInterface $e) {
} catch (\RuntimeException $e) {
}
This kind of granularity is really nice to work with. So nice that some
standards produced by PHP-FIG now ship them, such as
PSR-11, which ships a
ContainerExceptionInterface
and a NotFoundExceptionInterface
.
One thing we've started doing recently as we make packages support only PHP 7 versions is to have the marker
ExceptionInterface
extend theThrowable
interface; this ensures that implementations must be able to be thrown!
So, what happens when you're writing a one-off implementation of something that is expected to throw an exception matching one of these interfaces?
Why, use an anonymous class, of course!
As an example, I was writing up some documentation that illustrated a custom
ContainerInterface
implementation today, and realized I needed to throw an
exception at one point, specifically a Psr\Container\NotFoundExceptionInterface
.
I wrote up the following snippet:
$message = sprintf(/* ... */);
throw new class($message) extends RuntimeException implements
NotFoundExceptionInterface {
};
Done!
This works because RuntimeException
takes a message as the first
constructor argument; by extending that class, I gain that behavior. Since
NotFoundExceptionInterface
is a marker interface, I did not need to add any
additional behavior, so this inline example works out-of-the-box.
What else are you using anonymous classes for?