ParameterisedException
Since v1.2016041701
Description
ParameterisedException
is a base class that builds the exception's message from a supplied format string and data set. The data set is stored in the exception. This can be logged and/or inspected to help with debugging and troubleshooting.
Public Interface
ParameterisedException
has the following public interface:
/**
* ParameterisedException provides a data-oriented base class
* for your exceptions
*/
class ParameterisedException extends RuntimeException
{
/**
* default values for extra data
* @var array
*/
static protected $defaultExtras = [];
/**
* default filter for the call stack
* @var array
*/
static protected $defaultCallStackFilter = [];
/**
* our constructor
*
* You should call one of the 'newFromXXX()' methods to create a new
* exception to throw. These methods customise the format string and
* exception data for different contexts.
*
* @param string $formatString
* the sprintf() format string for our human-readable message
* @param array $data
* the data required to sprintf() with $formatString
* @param int $code
* the error code that you want to set (if any)
*/
public function __construct($formatString, $data = [], $code = 0);
/**
* create a new exception about your function / method's input parameter
*
* @param mixed $fieldOrVar
* the input parameter that you're throwing an exception about
* @param string $fieldOrVarName
* the name of the input parameter in your code
* @param array $extraData
* extra data that you want to include in your exception
* @param int|null $typeFlags
* do we want any extra type information in the final
* exception message?
* @param array $callStackFilter
* are there any namespaces we want to filter out of
* the call stack?
* @return ParameterisedException
* an fully-built exception for you to throw
*/
public static function newFromInputParameter(
$fieldOrVar,
$fieldOrVarName,
array $extraData = [],
$typeFlags = null,
array $callStackFilter = []
);
/**
* create a new exception about a value generated by / returned to your
* function or method
*
* @param mixed $fieldOrVar
* the value that you're throwing an exception about
* @param string $fieldOrVarName
* the name of the value in your code
* @param array $extraData
* extra data that you want to include in your exception
* @param int|null $typeFlags
* do we want any extra type information in the final
* exception message?
* @param array $callStackFilter
* are there any namespaces we want to filter out of
* the call stack?
* @return ParameterisedException
* an fully-built exception for you to throw
*/
public static function newFromVar(
$fieldOrVar,
$fieldOrVarName,
array $extraData = [],
$typeFlags = null,
array $callStackFilter = []
);
/**
* create the format string and exception data
*
* @param string $builder
* name of the ParameterBuilder class to use
* @param string $formatString
* the format string to customise
* @param array $backtrace
* a debug_backtrace()
* @param mixed $fieldOrVar
* the value that you're throwing an exception about
* @param string $fieldOrVarName
* the name of the value in your code
* @param array $extraData
* extra data that you want to include in your exception
* @param int|null $typeFlags
* do we want any extra type information in the
* final exception message?
* @param array $callStackFilter
* are there any namespaces we want to filter out of
* the call stack?
* @return array
* 0 - the formatString to use
* 1 - the exception data to use
*/
protected static function buildFormatAndData(
$builder,
$formatString,
$backtrace,
$fieldOrVar,
$fieldOrVarName,
array $extraData = [],
$typeFlags = null,
array $callStackFilter = []
);
/**
* what was the data that we used to create the printable message?
*
* @return array
*/
public function getMessageData();
/**
* what was the format string we used to create the printable message?
*
* @return string
*/
public function getMessageFormat();
}
How To Use
Creating Your Own Exceptions
The idea is that you shouldn't throw ParameterisedException
. You should extend
it to create your own exceptions. Here's a real example of how to do so.
namespace GanbaroDigital\ExceptionHelpers\V1\BaseExceptions;
use GanbaroDigital\HttpStatus\Interfaces\HttpRuntimeErrorException;
use GanbaroDigital\HttpStatus\StatusProviders\RuntimeError\
UnexpectedErrorStatusProvider;
class UnsupportedType
extends ParameterisedException
implements HttpRuntimeErrorException
{
static protected $defaultFormat =
"'%fieldOrVarName\$s' cannot be '%dataType\$s'";
// adds 'getHttpStatus()' that returns a HTTP 500 status value object
use UnexpectedErrorStatusProvider;
}
In this example, UnsupportedType
:
extends ParameterisedException
to create a new exception class- defines a format string as the
$defaultFormat
protected class property - maps onto the HTTP 500 status code by
implements HttpRuntimeErrorException
anduse UnexpectedErrorStatusProvider
together
There's no need for UnsupportedType
to create its own static factory methods. It automatically inherits the ones defined by ParameterisedException
. (They are discussed in more detail below).
Why You Need To Create Your Own Exception Classes
An exception's class name is used by PHP to tell what kind of error occurred.
- PHP's
catch
mechanism can only catch exceptions that use different classes. - Stack traces and error messages that go into your logs also use the exception class's name to tell you which error occurred.
You'll need this information when your app isn't working in production. When that happens, you're normally under a lot of pressure to understand the problem and fix it as quickly as possible. It's normally a very stressful event.
The more detailed information you have available about an error, the easier it is to deal with production problems without making things worse.
Static Factory Methods
Static factory methods are static
methods that create new exceptions for you to throw. Each static factory method should customise the exception's format string and exception data to suit the context that the exception is being thrown in.
ParameterisedException
defines the following static factory methods:
Factory Method | When To Use |
---|---|
::newFromInputParameter() |
when $fieldOrVar was passed to your function or method as a parameter |
::newFromVar() |
when $fieldOrVar is the return value from calling a function or method, or is a value created by your function or method |
Each static factory method returns a new exception for your code to throw
.
throw UnsupportedType::newFromInputParameter($className, '$className');
throw UnsupportedValue::newFromVar($statusCode, '$statusCode');
We expect to add new static factory methods in the future.
Why Static Factory Methods?
There's a different static factory method for each reason why you'd want to throw an exception. Each one tailors both the format string and exception data that gets built into the exception that is thrown. (Format strings and exception data are described further down the page).
We tailor these to make it easier to debug your application, both in your debugger and when reading your application logs.
The Format String
The format string is the first parameter passed to ParameterisedException::__construct()
.
Internally, ParameterisedException
uses vnsprintf()
from PHP: The Missing Bits. This allows you to use named parameters in the format string:
$format = "Value must be between %min\$s and %max\$s inclusively";
$data = [
'min' => 10,
'max' => 20,
];
throw new ParameterisedException($format, $data);
The Format String In Your Own Exceptions
Our static factory methods expect you to add static protected $defaultFormat
to your own exceptions. This will contain the format string that they will customise.
Factory Method | Customised Format String |
---|---|
::newFromInputParameter() |
%calledByName$s: %thrownByName$s says $defaultFormat |
::newFromVar() |
%thrownByName$s: $defaultFormat |
Exception Data
Each ParameterisedException
contains extra data for you to write to your logs or inspect in your debugger of choice. For example:
try {
throw NoSuchClass::newFromInputParameter('UndefinedClass');
}
catch (NoSuchClass $e) {
// extract the extra data
// getMessagedData() returns a PHP array
$exData = $e->getMessageData();
}
Here's the full list of the extra data that ParameterisedException
can provide.
Parameter | Type | Description |
---|---|---|
thrownBy |
CodeCaller |
details of the function or method that is throwing the exception |
thrownByName |
string | human-readable summary of thrownBy |
calledBy |
CodeCaller |
details of the function or method that has called the thrownBy function or method |
calledByName |
string | human-readable summary of calledBy |
fieldOrVarName |
string | the $fieldOrVarName passed into the factory method |
fieldOrVar |
mixed | the $fieldOrVar passed into the factory method |
dataType |
string | human-readable description of $fieldOrVar 's PHP type |
Here's a list of the extra data added by each factory method.
Factory Method | Extra Data Added |
---|---|
::newFromInputParameter() |
thrownBy , thrownByName , calledBy , calledByName , fieldOrVarName , fieldOrVar , dataType |
::newFromVar() |
thrownBy , thrownByName , fieldOrVarName , fieldOrVar , dataType |
Adding Extra Exception Data In Your Own Exceptions
Each static factory method takes a parameter $extraData
. This will be merged into the extra data added to your exception. Anything in $extraData
can be used in your format string:
class OutOfRange extends ParameterisedException
{
static protected $defaultFormat = "'%fieldOrVarName\$s' cannot be higher than '%maxRange\$d'";
}
throw OutOfRange::newFromVar($data, '$data', ['maxRange' => 100]);
Setting Default Values For Extra Exception Data In Your Own Exceptions
Each exception class can set the static
property $defaultExtras
to contain the default values of any extra data:
class OutOfRange extends ParameterisedException
{
static protected $defaultFormat = "'%fieldOrVarName\$s' cannot be higher than '%maxRange\$d'";
static protected $defaultExtras = ['maxRange' => 100];
}
Defaults mean that it is safe to call the static factory methods without providing an array of extra data:
throw OutOfRange::newFromVar($data, '$data');
But the defaults can still be overridden if necessary:
throw OutOfRange::newFromVar($data, '$data', ['maxRange' => 200]);
Filtering The Call Stack
The throwBy
, thrownByName
, calledBy
and calledByName
data is built when you call one of the static factory methods. A copy of the PHP call stack is taken, and the first two usable entries are used to build this data.
Sometimes, you'll want to skip over some of the entries in the call stack. This is normally done in libraries that are providing reusable robustness or quality checks, such as our own Contracts Library and Type-Checking Library.
You can filter the call stack by providing an array of classes as the $callStackFilter
parameter:
throw UnsupportedType::newFromVar($var, '$var', [], null, [self::class]);
You can also define a default call stack filter in your exception:
class ContractFailed extends ParameterisedException
{
// default filter for the call stack
static protected $defaultCallStackFilter = [
self::class,
static::class,
AssertValue::class,
CheckContracts::class,
Contracts::class,
];
}
// no need to provide a call stack filter
throw ContractFailed::newFromVar($var, '$var');
Class Contract
Here is the contract for this class:
GanbaroDigital\ExceptionHelpers\V1\BaseExceptions\ParameterisedException
[x] Can instantiate
[x] Is runtime exception
[x] Can pass code into constructor
[x] Sets exception message to formatted string
[x] Format string supports named parameters
[x] Can get message data
[x] Can get message format string
[x] can create from input parameter
[x] can create from input parameter with extra data
[x] can create from input parameter with default callerFilter
[x] can create from input parameter with callerFilter
[x] can create from PHP variable
[x] can create from PHP variable with extra data
[x] can create from PHP variable with default callerFilter
[x] can create from PHP variable with callerFilter
Class contracts are built from this class's unit tests.
Future releases of this class may add to this contract. New additions may include:
- clarifying existing behaviour (e.g. stricter contract around input or return types)
- add new behaviours (e.g. extra class methods)
When you use this class, you can only rely on the behaviours documented by this contract.
If you:
- find other ways to use this class,
- or depend on behaviours that are not covered by a unit test,
- or depend on undocumented internal states of this class,
... your code may not work in the future.
Notes
None at this time.
See Also
- Parameter builders - helpers for building the
$formatString
and$data
parameters for the constructor.
Changelog
v1.2016061902
-
$callerFilter
parameter is now known as$callStackFilter
We've renamed this parameter to make it easier to understand.
-
Added support for a default call stack filter
The static protected property
$defaultCallStackFilter
has been added.
v1.2016061901
-
::newFromInputParameter()
and::newFromVar()
methods addedUntil now, these methods have been defined on a subclass-by-subclass basis. As we built several subpackages, we discovered that these methods could be standardised and moved into this class.
These methods are now inherited from
ParameterisedException
. Their signature has changed to include the$extraData
parameter. Whilst this does break backwards-compatibility, Packagist is showing that no third-party packages will be affected at this time.