On this page
Features
Error Handling
We recommend that your code raise exceptions for conditions where it cannot
gracefully recover. Additionally, we recommend that you have a reasonable PHP
error_reporting
setting that includes warnings and fatal errors:
error_reporting(E_ALL & ~E_USER_DEPRECATED & ~E_DEPRECATED & ~E_STRICT & ~E_NOTICE);
If you follow these guidelines, you can then write or use middleware that does the following:
- sets an error handler that converts PHP errors to
ErrorException
instances. - wraps execution of the handler (
$handler->handle()
) with a try/catch block.
As an example:
function (ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
{
set_error_handler(function ($errno, $errstr, $errfile, $errline) {
if (! (error_reporting() & $errno)) {
// Error is not in mask
return;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
$response = $handler->handle($request);
return $response;
} catch (Throwable $e) {
}
restore_error_handler();
$response = new TextResponse(sprintf(
"[%d] %s\n\n%s",
$e->getCode(),
$e->getMessage(),
$e->getTraceAsString()
), 500);
}
You would then pipe this as the outermost (or close to outermost) layer of your application:
$app->pipe($errorMiddleware);
There Is an Easier Way
So that you do not need to do this, we provide an error handler for you, via
laminas-stratigility: Laminas\Stratigility\Middleware\ErrorHandler
.
This implementation allows you to both:
- provide a response generator, invoked when an error is caught; and
- register listeners to trigger when errors are caught.
We provide the factory Mezzio\Container\ErrorHandlerFactory
for
generating the instance; it should be mapped to the service
Laminas\Stratigility\Middleware\ErrorHandler
.
We provide two error response generators for you:
Mezzio\Middleware\ErrorResponseGenerator
, which optionally will accept aMezzio\Template\TemplateRendererInterface
instance, and a template name. When present, these will be used to generate response content; otherwise, a plain text response is generated that notes the request method and URI.
Since version 3.1.0, it also accepts a layout name, if you want to use one
other than layout::default
.
Mezzio\Middleware\WhoopsErrorResponseGenerator
, which uses whoops to present detailed exception and request information; this implementation is intended for development purposes.
Each also has an accompanying factory for generating the instance:
Mezzio\Container\ErrorResponseGeneratorFactory
Mezzio\Container\WhoopsErrorResponseGeneratorFactory
Map the service Mezzio\Middleware\ErrorResponseGenerator
to one of
these two factories in your configuration:
use Laminas\Stratigility\Middleware\ErrorHandler;
use Mezzio\Container;
use Mezzio\Middleware;
return [
'dependencies' => [
'factories' => [
ErrorHandler::class => Container\ErrorHandlerFactory::class,
Middleware\ErrorResponseGenerator::class => Container\ErrorResponseGeneratorFactory::class,
],
],
];
Use development mode configuration to enable whoops
You can specify the above in one of your
config/autoload/*.global.php
files, to ensure you have a production-capable error response generator.If you are using laminas-development-mode in your application (which is provided by default in the skeleton application), you can toggle usage of whoops by adding configuration to the file
config/autoload/development.local.php.dist
:use Mezzio\Container; use Mezzio\Middleware; return [ 'dependencies' => [ 'factories' => [ Middleware\WhoopsErrorResponseGenerator::class => Container\WhoopsErrorResponseGeneratorFactory::class, ], ], ];
When you enable development mode, whoops will then be enabled; when you disable development mode, you'll be using your production generator.
If you are not using laminas-development-mode, you can define a
config/autoload/*.local.php
file with the above configuration whenever you want to enable whoops.
Listening for errors
When errors occur, you may want to listen for them in order to provide
features such as logging. Laminas\Stratigility\Middleware\ErrorHandler
provides
the ability to do so via its attachListener()
method.
This method accepts a callable with the following signature:
function (
Throwable $error,
ServerRequestInterface $request,
ResponseInterface $response
) : void
The response provided is the response returned by your error response generator, allowing the listener the ability to introspect the generated response as well.
As an example, you could create a logging listener as follows:
<?php
namespace Acme;
use Exception;
use Psr\Log\LoggerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;
class LoggingErrorListener
{
/**
* Log format for messages:
*
* STATUS [METHOD] path: message
*/
const LOG_FORMAT = '%d [%s] %s: %s';
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function __invoke(Throwable $error, ServerRequestInterface $request, ResponseInterface $response)
{
$this->logger->error(sprintf(
self::LOG_FORMAT,
$response->getStatusCode(),
$request->getMethod(),
(string) $request->getUri(),
$error->getMessage()
));
}
}
You could then use a delegator factory to create your logger listener and attach it to your error handler:
<?php
namespace Acme;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
use Laminas\Stratigility\Middleware\ErrorHandler;
class LoggingErrorListenerDelegatorFactory
{
public function __invoke(ContainerInterface $container, string $name, callable $callback) : ErrorHandler
{
$listener = new LoggingErrorListener($container->get(LoggerInterface::class));
$errorHandler = $callback();
$errorHandler->attachListener($listener);
return $errorHandler;
}
}
Then, enable the delegator factory in your application, such as by adding the following to the getDependencies()
function in your app or module’s ConfigProvider.php
file.
public function getDependencies(): array
{
return [
'factories' => [
// …
],
'delegators' => [
ErrorHandler::class => [
LoggingErrorListenerDelegatorFactory::class,
],
],
];
}
## Handling more specific error types
You could also write more specific error handlers. As an example, you might want
to catch `UnauthorizedException` instances specifically, and display a login
page:
```php
function (ServerRequestInterface $request, RequestHandlerInterface $handler) use ($renderer) : ResponseInterface
{
try {
$response = $handler->handle($request);
return $response;
} catch (UnauthorizedException $e) {
}
return new HtmlResponse(
$renderer->render('error::unauthorized'),
401
);
}
You could then push this into a middleware pipe only when it's needed:
$app->get('/dashboard', [
$unauthorizedHandlerMiddleware,
$middlewareThatChecksForAuthorization,
$middlewareBehindAuthorizationWall,
], 'dashboard');
Page not found
Error handlers work at the outermost layer, and are used to catch exceptions and errors in your application. At the innermost layer of your application, you should ensure you have middleware that is guaranteed to return a response; this prevents errors in your application in the event that the application exhausts the middleware queue.
This in turn allows you to fully craft what sort of response is returned in such conditions.
Generally speaking, reaching the innermost middleware layer indicates that no middleware was capable of handling the request, and thus an HTTP 404 Not Found condition.
To simplify such responses, we provide
Mezzio\Handler\NotFoundHandler
. It will report a 404 response,
optionally using a composed template renderer to do so.
We provide a factory, Mezzio\Container\NotFoundHandlerFactory
, for
creating an instance, which we detail elsewhere. You should pipe it as the innermost layer of your application:
// A basic application:
$app->pipe(ErrorHandler::class);
// . . .
$app->pipe(RouteMiddleware::class);
// . . .
$app->pipe(DispatchMiddleware::class);
$app->pipe(NotFoundHandler::class);
If you wish to provide an alternate response status or use a canned response, you should provide your own handler and pipe it to your application.