Exceptions response code

Hi,

I noticed that when throwing exceptions other then slim’s http*Exception, the response code is always 500. For example, throw new ErrorException('Custom exception', 402); will render the error page with correct details:

Type: ErrorException
Code: 402
Message: Custom exception

But the headers respond with 500 nonetheless. I’m adding my error middleware as

$errorMiddleware = new ErrorMiddleware(
    $app->getCallableResolver(),
    $app->getResponseFactory(),
    $settings['displayErrorDetails'],
    $settings['logErrors'],
    $settings['logErrorDetails']
);

if ($settings['displayErrorDetails'] === false) {
    $errorHandler = $errorMiddleware->getDefaultErrorHandler();
    $errorHandler->registerErrorRenderer('text/html', HtmlErrorRenderer::class);
}

$app->add($errorMiddleware);

I can’t see what am I doing wrong. Any tips/help please?
Thanks lots & Merry Christmas

Slim can only map HTTP specific exceptions into a correct HTTP response and status code.

To implement a custom HTTP specific exception class try this:

<?php

namespace App\Exception;

use Slim\Exception\HttpSpecializedException;

final class HttpPaymentRequiredException extends HttpSpecializedException
{
    protected $code = 402;
    protected $message = 'Payment Required.';
    protected $title = '402 Payment Required';
    protected $description = 'The requested resource requires a payment.';
}

Usage:

throw new \App\Exception\HttpPaymentRequiredException($request);

The default Slim error handler will map the exception code into the HTTP response code.

If you want to throw non HTTP specific exceptions, you have to define a custom error handler and map the exception to the correct HTTP code.

Example:

$errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, $logErrors, $logErrorDetails);

$errorMiddleware->setErrorHandler(\App\Exception\MyCustomException::class, /* your handler */);

@odan, for explaining! I played a bit with re-throwing exceptions manually such as

<?php
public function read(Request $request, Response $response, array $args = []): Response
{
    $auth = new Auth($this->db);
    try {
        $users = $auth->user_read($args['uid']);
    } catch (Throwable $e) {
        if ($e->getCode() == 450) { throw new HttpNotFoundException($request, 'User not found.'); }
        if ($e->getCode() == 550) { throw new HttpBadRequestException($request, 'Wrong user id.'); }
        else { throw new HttpBadRequestException($request, 'Something went wrong, sorry.'); }
    }

which basically replaces a generic exception (i.e. a validation exception in user_read() function) with a friendlier and more relevant explanation which may change depending on where user_read() is used. I.e.:

  • when loading information about an authenticated user on each page load, i’d return a 401/unauthenticated if validation fails in user_read()
  • when showing a user’s profile, the user_read() would return a 404/not found on invalid input.

I suppose that writing my own exception handler is the only way to go here, which is what I totally wanted to avoid (not sure my skill set is up to par to do this correctly / optimally).

Since I didn’t find any prior art that would ease me into writing my own exception handler, it seems that hardly anyone has a need for it. This could mean, that I’m missing out important bits on how to make things simple and I’m over-engineering. Am I? Or does my use case have its validity?

You should distinguish between infrastructure specific exceptions (e.g. HttpNotFoundException, HttpBadRequestException) and domain specific exceptions (e.g. DomainException, ValidationException, etc…).

To map your domain specific exceptions into HTTP specific exceptions you could implement a middleware that catches your domain exceptions and re-throws them as a HTTP exception. Then you don’t have to create the mapping for each route.

1 Like

Thanks lots! I’ll look into handling this in a middleware.