A flexible and extensible exception handler for PHP applications.
This package can be installed via Composer:
composer require exception-handler/exception-handlerNote: This package requires PHP 8.3 or higher.
The core of this package is the ExceptionHandler class. It takes an ExceptionMetadataLoader and an array of middlewares in its constructor. The handle method is responsible for processing the exception.
Here's a basic example of how to set up the ExceptionHandler:
use ExceptionHandler\ExceptionHandler;
use ExceptionHandler\Metadata\ExceptionMetadataLoader;
// Create a metadata loader (we'll cover this in more detail later)
$metadataLoader = new ExceptionMetadataLoader(/* ... */);
// Create the exception handler
$exceptionHandler = new ExceptionHandler($metadataLoader);
// Handle an exception
try {
// ... your code that might throw an exception
} catch (Throwable $e) {
$metadata = $exceptionHandler->handle($e);
// ... do something with the metadata
}The exception handling process is built around a middleware system. You can add custom middlewares to the ExceptionHandler to add custom logic to the handling process. Middlewares are callables that receive the subject (the exception in the initial call) and the next middleware in the chain.
Here's an example of a simple logging middleware:
use Psr\Log\LoggerInterface;
$loggingMiddleware = function (Throwable $e, callable $next) use ($logger) {
$logger->error($e->getMessage());
return $next($e);
};
$exceptionHandler = new ExceptionHandler($metadataLoader, [$loggingMiddleware]);The ExceptionHandler adds its own middleware at the end of the chain, which calls the ExceptionMetadataLoader. The result of the handle method is the result of the last middleware in the chain.
The ExceptionHandler uses metadata to get information about an exception, such as the HTTP status code and a user-friendly message. The ExceptionMetadataLoader is responsible for loading this metadata. It iterates over a collection of MetadataLoaderInterface implementations until it finds one that supports the given exception.
This package provides three ways to configure exception metadata:
-
StaticListMetadataLoader: This loader uses a static list to map exception classes to metadata factories. See the Exception Hierarchy section for more details.use ExceptionHandler\Lib\StaticList; use ExceptionHandler\Metadata\ExceptionMetadata; use ExceptionHandler\Metadata\MetadataLoaders\StaticListMetadataLoader; $list = new StaticList([ MyException::class => static fn (MyException $e): ExceptionMetadata => new ExceptionMetadata(404, 'Not Found'), ]); $metadataLoader = new StaticListMetadataLoader($list);
-
AttributeMetadataLoader: This loader uses PHP 8 attributes to define metadata on the exception class itself.use ExceptionHandler\Metadata\MetadataLoaders\Attribute\ThrowableMetadata; #[ThrowableMetadata(code: 400, message: 'Bad Request')] class MyException extends Exception { }
-
MetadataAwareExceptionMetadataLoader: This loader works with exceptions that implement theMetadataAwareExceptionInterface.use ExceptionHandler\Metadata\ExceptionMetadata; use ExceptionHandler\Metadata\MetadataLoaders\MetadataAware\MetadataAwareExceptionInterface; class MyException extends Exception implements MetadataAwareExceptionInterface { public function getMetadata(): ExceptionMetadata { return new ExceptionMetadata(403, 'Forbidden'); } }
You can combine multiple metadata loaders by passing them as an array to the ExceptionMetadataLoader constructor.
$metadataLoader = new ExceptionMetadataLoader([
new MetadataAwareExceptionMetadataLoader(),
new AttributeMetadataLoader(),
new StaticListMetadataLoader($list),
]);This package is designed to be integrated into a web application. The HttpResponseMiddleware is a middleware that can be added to the ExceptionHandler to transform the ExceptionMetadata into an HTTP response.
The HttpResponseMiddleware takes an HttpRequestProviderInterface and a ControllerInterface as dependencies. It retrieves the current HTTP request from the provider and calls the controller with the request and the ExceptionMetadata.
use ExceptionHandler\Http\HttpResponseMiddleware;
$httpResponseMiddleware = new HttpResponseMiddleware($httpRequestProvider, $controller);
$exceptionHandler = new ExceptionHandler($metadataLoader, [$httpResponseMiddleware]);
$response = $exceptionHandler->handle($e); // $response will be a PSR-7 ResponseInterfaceThe ControllerInterface is responsible for creating the final HTTP response. You need to provide your own implementation of this interface.
Here's an example of a simple JSON controller:
use ExceptionHandler\Http\Controller\ControllerInterface;
use ExceptionHandler\Metadata\ExceptionMetadata;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseFactoryInterface;
class JsonController implements ControllerInterface
{
public function __construct(private ResponseFactoryInterface $responseFactory)
{
}
public function __invoke(MessageInterface $request, ExceptionMetadata $metadata): MessageInterface
{
$response = $this->responseFactory->createResponse($metadata->getCode());
$response->getBody()->write(json_encode(['message' => $metadata->getMessage()]));
return $response->withHeader('Content-Type', 'application/json');
}
}The package provides a TranslationMiddleware that allows you to translate exception messages.
The TranslationMiddleware requires a TranslationConfigLoader and a TranslatorInterface. It loads the translation configuration for the given exception and uses the translator to get the translated message.
use ExceptionHandler\Translation\TranslationMiddleware;
$translationMiddleware = new TranslationMiddleware($configLoader, $translator);
$exceptionHandler = new ExceptionHandler($metadataLoader, [$translationMiddleware]);You need to provide your own implementation of the TranslatorInterface. This interface is compatible with the Symfony\Contracts\Translation\TranslatorInterface.
use ExceptionHandler\Translation\TranslatorInterface;
class MyTranslator implements TranslatorInterface
{
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
// ... your translation logic
}
}Similar to the metadata loaders, there are multiple ways to configure translation for an exception. The TranslationConfigLoader can be configured with different loaders.
-
StaticListTranslationConfigLoader: This loader uses a static list to map exception classes to translation configurations. See the Exception Hierarchy section for more details.use ExceptionHandler\Lib\StaticList; use ExceptionHandler\Translation\TranslationConfig; use ExceptionHandler\Translation\TranslationConfigLoaders\StaticListTranslationConfigLoader; $list = new StaticList([ MyException::class => static fn (MyException $e): TranslationConfig => new TranslationConfig('my_exception_message'), ]); $configLoader = new StaticListTranslationConfigLoader($list);
-
AttributeTranslationConfigLoader: This loader uses PHP 8 attributes to define the translation configuration on the exception class itself.use ExceptionHandler\Translation\TranslationConfigLoaders\Attribute\TranslationConfig; #[TranslationConfig(id: 'my_exception_message')] class MyException extends Exception { }
-
TranslationConfigAwareTranslationConfigLoader: This loader works with exceptions that implement theTranslationConfigAwareInterface.use ExceptionHandler\Translation\TranslationConfig; use ExceptionHandler\Translation\TranslationConfigLoaders\TranslationConfigAware\TranslationConfigAwareInterface; class MyException extends Exception implements TranslationConfigAwareInterface { public function getTranslationConfig(): TranslationConfig { return new TranslationConfig('my_exception_message'); } }
You can combine multiple translation config loaders by passing them as an array to the TranslationConfigLoader constructor.
$configLoader = new TranslationConfigLoader([
new TranslationConfigAwareTranslationConfigLoader(),
new AttributeTranslationConfigLoader(),
new StaticListTranslationConfigLoader($list),
]);Please refer to the source code for more details on how to configure translation.
Both StaticListMetadataLoader and StaticListTranslationConfigLoader intelligently handle exception hierarchies. When looking for a configuration, they will traverse up the exception's class chain, including parent classes and implemented interfaces. This is useful for setting up metadata and translations that apply to a single exception class or to a related group of exceptions.
This package is released without a license.