meritum/http-exception-handler

meritum/http exception handler that translates exceptions into structured JSON error responses using the meritum/structured-logging pipeline

Maintainers

Package info

github.com/MeritumIO/http-exception-handler

pkg:composer/meritum/http-exception-handler

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-06-12 19:13 UTC

This package is auto-updated.

Last update: 2026-06-12 19:15:36 UTC


README

HTTP exception handler that translates exceptions into structured JSON error responses using the meritum/structured-logging pipeline.

Requirements

  • PHP 8.4+
  • georgeff/kernel ^1.6
  • meritum/http ^1.0
  • meritum/structured-logging ^1.0

Installation

composer require meritum/http-exception-handler

Usage

Module registration

Register ExceptionHandlerModule alongside StructuredLoggingModule. StructuredLoggingModule requires a LoggerInterface to already be registered:

use Meritum\HttpExceptionHandler\ExceptionHandlerModule;
use Meritum\StructuredLogging\StructuredLoggingModule;

$kernel->define(LoggerInterface::class, fn() => new MyLogger());
$kernel->addModule(new StructuredLoggingModule());
$kernel->addModule(new ExceptionHandlerModule());
$kernel->boot();

ExceptionHandlerModule registers:

  • ExceptionHandlerInterface — the handler meritum/http resolves when an exception reaches the kernel boundary
  • HttpExceptionTranslationHandler — tagged as exception.translator.handlers, translates HttpExceptionInterface instances into structured domain exceptions

How it works

When an exception reaches the HTTP kernel boundary:

  1. ExceptionHandlerInterface::handle() is called with the exception and the current request
  2. The exception is passed through the meritum/structured-logging translate→report pipeline — it is translated to a domain exception and logged
  3. An ErrorEnvelope is built from the domain exception and returned as a JSON response

HTTP exceptions (implementing HttpExceptionInterface) are translated by HttpExceptionTranslationHandler into an HttpDomainException, which carries the request context and sets the appropriate log severity. Any other exception falls through to the structured-logging catch-all translator and produces a generic 500 response.

Response format

All error responses use a consistent JSON envelope:

{
  "code":   "HTTP_404",
  "status": 404,
  "title":  "Not Found",
  "detail": "The requested resource could not be found."
}
  • code — the domain exception error code; HTTP_ followed by the HTTP status code for HTTP exceptions
  • status — the HTTP status code as an integer; also set as the response status
  • title — the HTTP exception title; "Unexpected Error" for non-HTTP exceptions
  • detail — the exception message; may be null

Custom HTTP exceptions

Extend HttpException or implement HttpExceptionInterface directly to define domain-specific HTTP exceptions:

use Meritum\Http\Exception\HttpException;

final class ResourceNotFoundException extends HttpException
{
    protected string $title = 'Not Found';
    protected int $status = 404;
}

Throw the exception from within the request pipeline:

throw new ResourceNotFoundException($request, 'User 42 does not exist.');

HttpExceptionTranslationHandler matches any HttpExceptionInterface, so custom exceptions are handled automatically with no additional registration.

Custom translation handlers

To produce a different domain exception for a specific HTTP exception type, implement TranslationHandler and register it at a higher priority than the default handler (0):

use Throwable;
use Meritum\StructuredLogging\TranslationHandler;
use Meritum\StructuredLogging\Exception\DomainException;

final class ValidationExceptionTranslationHandler implements TranslationHandler
{
    public function matches(Throwable $exception): bool
    {
        return $exception instanceof ValidationHttpException;
    }

    public function handle(Throwable $exception): DomainException
    {
        assert($exception instanceof ValidationHttpException);

        return new ValidationDomainException($exception);
    }

    public function priority(): int
    {
        return 1;
    }
}

Register and tag it in your module:

$kernel->define(ValidationExceptionTranslationHandler::class, fn() => new ValidationExceptionTranslationHandler())
       ->tag('exception.translator.handlers');

Because this handler runs at priority 1, it matches before HttpExceptionTranslationHandler (0) for ValidationHttpException instances. All other HTTP exceptions continue to be handled by the default.

Using ErrorEnvelope directly

ErrorEnvelope is public API. Use it to build consistent error responses anywhere in your application:

use Meritum\HttpExceptionHandler\ErrorEnvelope;

// From a caught domain exception
$envelope = ErrorEnvelope::fromDomainException($domainException);

// With explicit values
$envelope = new ErrorEnvelope('HTTP_403', 403, 'Forbidden', 'You do not have permission.');

return new JsonResponse($envelope, $envelope->status);

License

MIT