-
Notifications
You must be signed in to change notification settings - Fork 0
4.0: Exceptables
The IsExceptable
trait provides a complete base implementation for the Exceptable
interface. A working implementation is just a few simple steps:
-
extend
aThrowable
class -
implement
theExceptable
interface -
use
theIsExceptable
trait - define your error codes and
INFO
.
Even easier, however, is to extend
from one of the SplExceptables.
Here's a brief example Exceptable:
<?php
use AT\Exceptable\Spl\RuntimeException;
class FooException extends RuntimeException {
// define your error code.
const UNKNOWN_FOO = 1;
// define information about your errors (indexed by error code).
// at a minimum, include a message.
const INFO = [
self::UNKNOWN_FOO => ["message" => "unknown foo"]
];
// that's it
}
Exceptables have very straightforward constructors. The first, and often only, argument you'll need to provide is the error code:
<?php
throw new FooException(FooException::UNKNOWN_FOO);
// Fatal error: Uncaught FooException: unknown foo ...
Note, our Exceptable set the proper exception message for us. But, this message is generic and fairly useless. Let's add some $context
.
<?php
use AT\Exceptable\Spl\RuntimeException;
class FooException extends RuntimeException {
const UNKNOWN_FOO = 1;
const INFO = [
self::UNKNOWN_FOO => [
"message" => "unknown foo",
"format" => "i don't know who, you think is foo, but it's not {foo}"
]
];
}
Here, format
is a message formatting string. It takes named {placeholders}
from contextual information your code will provide at runtime. If a value for a named placeholder is not provided, then the Exceptable will fall back on using the default message.
<?php
throw new FooException(FooException::UNKNOWN_FOO, ["foo" => "foobedobedoo"]);
// Fatal error: Uncaught FooException: i don't know who, you think is foo, but it's not foobedobedoo ...
Uncaught exceptions are great and all, but what if we want to catch them? How do we know what to do with them? Because your error conditions have codes, your program can read Exceptables almost as well as you can - without looking at the message at all.
<?php
use AT\Exceptable\Spl\RuntimeException;
class FooException extends RuntimeException {
const UNKNOWN_FOO = 1;
const SPOOKY_FOO = 2;
const INFO = [
self::UNKNOWN_FOO => [
"message" => "unknown foo",
"format" => "i don't know who, you think is foo, but it's not {foo}"
],
self::SCARY_FOO => [
"message" => "woooo spooky foo",
"format" => "Ph'nglui mglw'nafh {foo} R'lyeh wgah'nagl fhtagn"
]
];
}
<?php
try {
$e = rand(0, 1) ?
new FooException(FooException::UNKNOWN_FOO, ["foo" => "foobedobedoo"]) :
new FooException(FooException::SPOOKY_FOO, ["foo" => "Cthulhu"]);
throw $e;
} catch (FooException $e) {
if (FooException::is($e, FooException::UNKNOWN_FOO)) {
// we know how to handle this; everyone can be happy
introduceFoo($e->getContext()["foo"]);
} else {
// nnnope
RUN_AWAY_RUN_AWAY($e->getRoot()->getMessage());
}
}
In the above examples, you might have noticed some of those useful utilities.
We can use the is()
method to identify Exceptables by their class and code.
Since we can pass a $context
array to the Exceptable, it makes sense that we'd have a getContext()
method to get it back.
When you have a chain of previous exception(s), it's common that the initial exception is of more interest than other, intermediate exceptions; so we have getRoot()
to get it directly (if there is no previous exception, no problem).
There is a test suite for the base IsExceptable
trait, which is also be useful as a starting point for testing your own Exceptables. Run it with composer test:unit
.