Slim Framework 3 is being actively developed at the moment and has a number of changes in it, including the use of the Pimple DI container and an overhaul of pretty much everything else! In this post, I'm going to look at error handling.
The default error handler in Slim 3 is Slim\Handlers\Error. It's fairly simple and renders the error quite nicely, setting the HTTP status to 500.
I want to log these errors via monolog.
Firstly, we set up a logger in the DIC:
$app['Logger'] = function($container) { $logger = new Monolog\Logger('logger'); $filename = _DIR__ . '/../log/error.log'; $stream = new Monolog\Handler\StreamHandler($filename, Monolog\Logger::DEBUG); $fingersCrossed = new Monolog\Handler\FingersCrossedHandler( $stream, Monolog\Logger::ERROR); $logger->pushHandler($fingersCrossed); return $logger; };
Now, we can create our own error handler which extends the standard Slim one as all we want to do is add logging.
<?php namespace App\Handlers; use Psr\Http\Message\RequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Monolog\Logger; final class Error extends \Slim\Handlers\Error { protected $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function __invoke(Request $request, Response $response, \Exception $exception) { // Log the message $this->logger->critical($exception->getMessage()); return parent::__invoke($request, $response, $exception); } }
The error handler implements __invoke(), so our new class overrides this function, extracts the message and logs it as a critical. To get the Logger into the error handler, we use standard Dependency Injection techniques and write a constructor that takes the configured logger as a parameter.
All we need to do now is register our new error handler which we can do in index.php:
$app['errorHandler'] = function ($c) { return new App\Handlers\Error($c['Logger']); };
Again, this is standard Pimple, so the 'errorHandler' key takes a closure which receives an instance of the container, $c. We instantiate a new App\Handlers\Error object and then retrieve the Logger from the container as we have already registered that with Pimple, so it knows how to create one for us.
With this done, we now have a new error handler in place. From the user's point of view, there's no difference, but we now get a message in our log file when something goes wrong.
Other error handlers
Obviously, we can use this technique to replace the entire error handler for situations when we don't want to display a comprehensive developer-friendly error to the user. Another case would be if we are writing an API, we may not want to respond with an HTML error page.
In these cases, we do exactly the same thing. For example, if we're writing a JSON API, then a suitable error handler looks like this:
<?php namespace App\Handlers; use Psr\Http\Message\RequestInterface as Request; use Psr\Http\Message\ResponseInterface as Response; use Monolog\Logger; final class ApiError extends \Slim\Handlers\Error { protected $logger; public function __construct(Logger $logger) { $this->logger = $logger; } public function __invoke(Request $request, Response $response, \Exception $exception) { // Log the message $this->logger->critical($exception->getMessage()); // create a JSON error string for the Response body $body = json_encode([ 'error' => $exception->getMessage(), 'code' => $exception->getCode(), ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); return $response ->withStatus(500) ->withHeader('Content-type', 'application/json') ->withBody(new Body(fopen('php://temp', 'r+'))) ->write($body); } }
This time we construct a JSON string for our response and then use Slim's PSR-7-compatible Response object to create a new one with the correct information in it, which we then return to the client.
Fin
As you can see, it's really easy to manipulate and control error handling in Slim 3. Compared to Slim 2, the best bit is that the PrettyExceptions middleware is not automatically added which had always annoyed me when writing APIs.