Slim 4 - Error handling
source link: https://odan.github.io/2020/05/27/slim4-error-handling.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Daniel's Dev Blog
Developer, Trainer, Open Source Contributor
Blog About me DonateSlim 4 - Error handling
27 May 2020
Table of contents
Requirements
- PHP 7.2+
- Composer (dev environment)
- A Slim 4 application
- The Monolog LoggerFactory
Introduction
Several types of errors can occur in a web application.
Depending on the type, different strategies must be chosen for catching and handling them.
For example, how to handle exceptions using the integrated middleware is already well described in the documentation.
Therefore I would like to focus on special types of errors that are not yet fully documented.
Please note, that you may not need all of these error handler in your app. Just pick what you realy need.
But before we start you should install a PSR-3 logger implementation, like Monolog.
Read more
Catching 404 not found errors
In Slim 3 you were able to add the custom “Not Found Handler” to handle undefined routes.
Since Slim 4 it’s possible to add a custom error handler to the ErrorMiddleware
.
Here you can find some examples how to add a custom error handler
The simplest way to catch 404 (and other) http errors is to add a custom middleware before the ErrorMiddleware, e.g.:
<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpNotFoundException;
use Slim\Middleware\ErrorMiddleware;
use Slim\Psr7\Response;
// ...
// HttpNotFound Middleware
$app->add(function (
ServerRequestInterface $request,
RequestHandlerInterface $handler
) {
try {
return $handler->handle($request);
} catch (HttpNotFoundException $httpException) {
$response = (new Response())->withStatus(404);
$response->getBody()->write('404 Not found');
return $response;
}
});
$app->add(ErrorMiddleware::class);
Of course this example is very limited in its functionality. To catch
all http errors you could use the HttpException
within the catch block.
If you want to log all http errors or render Twig templates
then you should implement a custom Slim DefaultErrorHandler or a
HttpExceptionMiddleware
.
Example
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Slim\Exception\HttpException;
final class HttpExceptionMiddleware implements MiddlewareInterface
{
/**
* @var ResponseFactoryInterface
*/
private $responseFactory;
public function __construct(ResponseFactoryInterface $responseFactory)
{
$this->responseFactory = $responseFactory;
}
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
try {
return $handler->handle($request);
} catch (HttpException $httpException) {
// Handle the http exception here
$statusCode = $httpException->getCode();
$response = $this->responseFactory->createResponse()->withStatus($statusCode);
$errorMessage = sprintf('%s %s', $statusCode, $response->getReasonPhrase());
// Log the errror message
// $this->logger->error($errorMessage);
// Render twig template or just add the content to the body
$response->getBody()->write($errorMessage);
return $response;
}
}
}
Add the middleware to the stack, e.g. in config/middleware.php
:
<?php
use App\Middleware\HttpExceptionMiddleware;
use Slim\Middleware\ErrorMiddleware;
// ...
$app->add(HttpExceptionMiddleware::class); // <--- here
$app->add(ErrorMiddleware::class);
To create the HttpExceptionMiddleware
automatically via autowiring,
you need the following container definition for ResponseFactoryInterface::class
.
<?php
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\App;
// ...
return [
// ...
ResponseFactoryInterface::class => function (ContainerInterface $container) {
return $container->get(App::class)->getResponseFactory();
},
];
Catching PHP warnings, notices and errors
Besides Throwable PHP has its older error level system of warnings, notices and errors.
To “catch” this type of PHP error, you can add a custom error handler
using the set_error_handler
function.
Since this can be solved even more elegantly in a slim application, we integrate this functionality into a middleware.
Example
File: src/Middleware/ErrorHandlerMiddleware.php
Source code:
<?php
namespace App\Middleware;
use App\Factory\LoggerFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
/**
* Middleware.
*/
final class ErrorHandlerMiddleware implements MiddlewareInterface
{
/**
* @var LoggerInterface
*/
private $logger;
/**
* The constructor.
*
* @param LoggerFactory $loggerFactory The logger
*/
public function __construct(LoggerFactory $loggerFactory)
{
$this->logger = $loggerFactory
->addFileHandler('errors.log')
->createInstance('error_handler_middleware');
}
/**
* Invoke middleware.
*
* @param ServerRequestInterface $request The request
* @param RequestHandlerInterface $handler The handler
*
* @return ResponseInterface The response
*/
public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
): ResponseInterface
{
$errorTypes = E_ALL;
// Set custom php error handler
set_error_handler(
function ($errno, $errstr, $errfile, $errline) {
switch ($errno) {
case E_USER_ERROR:
$this->logger->error(
"Error number [$errno] $errstr on line $errline in file $errfile"
);
break;
case E_USER_WARNING:
$this->logger->warning(
"Error number [$errno] $errstr on line $errline in file $errfile"
);
break;
default:
$this->logger->notice(
"Error number [$errno] $errstr on line $errline in file $errfile"
);
break;
}
// Don't execute PHP internal error handler
return true;
},
$errorTypes
);
return $handler->handle($request);
}
}
In order to make it work, the ErrorHandlerMiddleware::class
must be added before the Slim ErrorMiddleware:
<?php
use App\Middleware\ErrorHandlerMiddleware;
// ...
$app->add(ErrorHandlerMiddleware::class); // <-- here
$app->addErrorMiddleware(true, true, true, $logger);
To see if it works, try running code like this in your code base:
$array = [];
$test = $array['nada'];
The expected logfile output should look like this:
[2020-05-25 10:10:05] app.NOTICE: Error number [8] Undefined index:
nada on line 30 in file filename.php [] []
PS: Another way to achieve the same effect is to register a custom shutdown handler.
Read more
Catching invalid HTTP requests
You may already have received the following error message.
Bad Request: Header name must be an RFC 7230 compatible string.
This can happen because the PSR-7 implementation doesn’t support numeric header names.
Since nyholm/psr7
v1.3.0 numeric header names are supported.
You just need to update to the latest version and the issue is fixed.
As a workaround (for all other PSR-7 implementations)
you can try to catch all exceptions of the $app->run()
method.
<?php
// bootstrap code ...
try {
$app->run();
} catch (Throwable $exception) {
http_response_code(400);
echo sprintf('Bad Request: %s', $exception->getMessage());
}
Read more
© 2020 Daniel Opitz | Twitter
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK