maduser / argon-error
Exception policy and error response handling for the Argon runtime stack.
Requires
- maduser/argon-container: ^1.3
- maduser/argon-support: ^1.0
- psr/http-factory: ^1.1
- psr/http-message: ^2.0
- psr/log: ^3.0
Requires (Dev)
- dealerdirect/phpcodesniffer-composer-installer: ^1.1
- phpunit/phpunit: ^11.5
- slevomat/coding-standard: ^8.24
- squizlabs/php_codesniffer: ^4.0
- vimeo/psalm: ^6.13
This package is auto-updated.
Last update: 2026-05-24 14:29:21 UTC
README
maduser/argon-error is the HTTP error-handling layer for Argon applications.
It turns uncaught throwables into PSR-7 responses, lets applications register
exception-specific reporting and rendering policies, and integrates with the
shared Argon runtime error-handler contract.
The package stays intentionally small:
ErrorHandlerbridges Argon runtime failures to the dispatcher/formatter stack.ExceptionDispatcherruns registered exception policies before falling back to the formatter.ExceptionFormattercreates JSON or plain-text PSR-7 responses.ErrorHandlerServiceProviderwires the package into anArgonContainer.
Installation
composer require maduser/argon-error
The formatter depends on PSR-17 response and stream factories. In a full Argon
stack those are normally provided by maduser/argon-http-message.
Service Provider
Register the provider during application boot:
use Maduser\Argon\Container\ArgonContainer; use Maduser\Argon\Error\Provider\ErrorHandlerServiceProvider; $container = new ArgonContainer(); $container->register(ErrorHandlerServiceProvider::class); $container->boot();
The provider binds:
Maduser\Argon\Support\Contracts\ErrorHandlerInterfaceMaduser\Argon\Error\Contracts\ExceptionDispatcherInterfaceMaduser\Argon\Error\Contracts\ExceptionFormatterInterfaceMaduser\Argon\Error\Contracts\ExceptionPolicyRegistryInterface
Any service tagged as ExceptionPolicyInterface can register custom exception
reporting and rendering during container boot.
use App\Http\AppExceptionPolicy; use Maduser\Argon\Error\Contracts\ExceptionPolicyInterface; $container->set(AppExceptionPolicy::class)->tag(ExceptionPolicyInterface::class);
Exception Policies
Policies separate side effects from response creation:
- reporters run first for all matching exception types;
- renderers run after reporters and may return a
ResponseInterface; - a renderer returning
nulllets the dispatcher continue; - if no renderer returns a response, the formatter creates the fallback response.
use Maduser\Argon\Error\Contracts\ExceptionPolicyInterface; use Maduser\Argon\Error\Contracts\ExceptionPolicyRegistryInterface; use App\Exceptions\PaymentFailed; use Psr\Http\Message\ResponseFactoryInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\StreamFactoryInterface; use Psr\Log\LoggerInterface; use RuntimeException; use Throwable; final readonly class AppExceptionPolicy implements ExceptionPolicyInterface { public function __construct( private LoggerInterface $logger, private ResponseFactoryInterface $responses, private StreamFactoryInterface $streams, ) { } public function register(ExceptionPolicyRegistryInterface $exceptions): void { $exceptions->report( Throwable::class, fn(Throwable $e, ServerRequestInterface $request): void => $this->logger->error( $e->getMessage(), ['exception' => $e, 'path' => $request->getUri()->getPath()] ) ); $exceptions->report( PaymentFailed::class, fn(PaymentFailed $e): void => $this->notifyBillingChannel($e) ); $exceptions->render( RuntimeException::class, fn(RuntimeException $e): ?ResponseInterface => $this->responses ->createResponse(500) ->withHeader('Content-Type', 'application/json') ->withBody($this->streams->createStream('{"error":"Runtime failure"}')) ); } }
Renderer selection is deterministic. More specific exception classes win before parent classes or interfaces. If two renderers are registered for the same specificity, registration order wins.
Reporter and renderer failures are logged and swallowed. Exception handling must not fail because an application callback failed.
Formatting
ExceptionFormatter uses the request Accept header:
application/jsonreturns a JSON error payload.- anything else returns
text/plain.
HTTP status resolution is deliberately conservative:
- exceptions implementing
HttpExceptionInterfacemay provide an explicit status code; - otherwise throwable codes in the
400..599range are used; - invalid or non-HTTP codes fall back to
500.
Stack traces are hidden by default. They are included only when debug mode is
enabled or the exception implements SafeToDisplayExceptionInterface.