PHPackages                             phpdot/error-handler - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Debugging &amp; Profiling](/categories/debugging)
4. /
5. phpdot/error-handler

ActiveLibrary[Debugging &amp; Profiling](/categories/debugging)

phpdot/error-handler
====================

Modern error handler with beautiful debug pages, RFC 9457 JSON errors, customizable renderers, solution providers, and PSR-15 middleware support.

v1.0.4(4d ago)022↓86.7%MITPHPPHP &gt;=8.4

Since Apr 4Pushed 4d agoCompare

[ Source](https://github.com/phpdot/error-handler)[ Packagist](https://packagist.org/packages/phpdot/error-handler)[ RSS](/packages/phpdot-error-handler/feed)WikiDiscussions main Synced today

READMEChangelogDependencies (18)Versions (6)Used By (0)

phpdot/error-handler
====================

[](#phpdoterror-handler)

Modern error handler with beautiful debug pages, RFC 9457 JSON errors, customizable renderers, solution providers, and PSR-15 middleware support. One-line setup. Standalone.

---

Table of Contents
-----------------

[](#table-of-contents)

- [Install](#install)
- [Quick Start](#quick-start)
- [Architecture](#architecture)
    - [Dispatch Pipeline](#dispatch-pipeline)
    - [Package Structure](#package-structure)
- [ErrorHandler (Global Registration)](#errorhandler-global-registration)
    - [One-Line Setup](#one-line-setup)
    - [Full Configuration](#full-configuration)
    - [What Gets Registered](#what-gets-registered)
- [ExceptionHandler (The Core)](#exceptionhandler-the-core)
    - [Direct Usage](#direct-usage)
    - [Status Code Mapping](#status-code-mapping)
    - [JSON Detection](#json-detection)
- [Renderers](#renderers)
    - [HtmlDevRenderer](#htmldevrenderer)
    - [HtmlProdRenderer](#htmlprodrenderer)
    - [JsonRenderer (RFC 9457)](#jsonrenderer-rfc-9457)
    - [PlainTextRenderer](#plaintextrenderer)
    - [Custom Renderers](#custom-renderers)
- [Dev Debug Page](#dev-debug-page)
    - [Features](#dev-page-features)
    - [Customizing the Template](#customizing-the-template)
- [Production Error Page](#production-error-page)
- [Solution Providers](#solution-providers)
    - [SolutionProviderInterface](#solutionproviderinterface)
    - [Solution and SolutionLink DTOs](#solution-and-solutionlink-dtos)
    - [Building a Solution Provider](#building-a-solution-provider)
- [Context Providers](#context-providers)
    - [ContextProviderInterface](#contextproviderinterface)
    - [Building a Context Provider](#building-a-context-provider)
- [PSR-15 Middleware](#psr-15-middleware)
- [Stack Trace and Frames](#stack-trace-and-frames)
    - [StackTrace](#stacktrace)
    - [Frame](#frame)
    - [CodeLine](#codeline)
- [ErrorContext (The Data Contract)](#errorcontext-the-data-contract)
- [Environment Filtering](#environment-filtering)
- [PHP Error Conversion](#php-error-conversion)
- [Fatal Error Catching](#fatal-error-catching)
- [Exception Handling](#exception-handling)
- [Comparison](#comparison)
- [API Reference](#api-reference)
    - [ErrorHandler API](#errorhandler-api)
    - [ExceptionHandler API](#exceptionhandler-api)
    - [ErrorHandlerMiddleware API](#errorhandlermiddleware-api)
    - [Renderers API](#renderers-api)
    - [Context DTOs API](#context-dtos-api)
    - [Solution DTOs API](#solution-dtos-api)
    - [Contracts API](#contracts-api)
    - [FatalErrorException API](#fatalerrorexception-api)
- [License](#license)

---

Install
-------

[](#install)

```
composer require phpdot/error-handler
```

RequirementVersionPHP&gt;= 8.3psr/http-message^2.0psr/http-server-middleware^1.0psr/log^3.0Zero phpdot dependencies. Works with any PSR-7 implementation.

---

Quick Start
-----------

[](#quick-start)

```
use PHPdot\ErrorHandler\ErrorHandler;

// One line. Works immediately.
ErrorHandler::register('development');
```

That's it. Uncaught exceptions get a beautiful debug page. PHP warnings and notices become exceptions. Fatal errors are caught on shutdown.

Switch to production:

```
ErrorHandler::register('production');
```

Clean error pages. No internals exposed. Same handler.

---

Architecture
------------

[](#architecture)

### Dispatch Pipeline

[](#dispatch-pipeline)

```
Exception caught
    ↓
ExceptionHandler::handle($exception, $request?)
    │
    ├── 1. Collect
    │   ├── Build StackTrace (frames with code snippets)
    │   ├── Collect request info (if PSR-7 available)
    │   ├── Run ContextProviders (queries, routes, custom tabs)
    │   ├── Run SolutionProviders (suggested fixes)
    │   ├── Filter environment (mask sensitive keys)
    │   └── Package into ErrorContext DTO
    │
    ├── 2. Log (PSR-3)
    │   └── logger->log(level, message, [exception, status_code])
    │   └── 5xx → error, 4xx → warning, other → notice
    │
    ├── 3. Render
    │   ├── Accept: application/json → JsonRenderer (RFC 9457)
    │   ├── Development → HtmlDevRenderer (debug page)
    │   ├── Production → HtmlProdRenderer (clean page)
    │   └── CLI → PlainTextRenderer
    │
    └── 4. Output
        ├── Standalone: echo + http_response_code()
        └── Middleware: PSR-7 Response

```

### Package Structure

[](#package-structure)

```
src/
├── ErrorHandler.php                    # Static register() — one-line setup
├── ExceptionHandler.php                # Core: collect, log, render
├── Middleware/
│   └── ErrorHandlerMiddleware.php      # PSR-15 middleware
├── Context/
│   ├── ErrorContext.php                # DTO — all data for rendering
│   ├── StackTrace.php                 # Parsed trace with code snippets
│   ├── Frame.php                      # Single stack frame
│   ├── CodeLine.php                   # Single line of source code
│   └── ContextTab.php                 # Named tab of extra debug data
├── Contract/
│   ├── RendererInterface.php           # ErrorContext → string
│   ├── ContextProviderInterface.php    # Extra debug tabs
│   └── SolutionProviderInterface.php   # Suggested fixes
├── Renderer/
│   ├── HtmlDevRenderer.php             # Beautiful dev debug page
│   ├── HtmlProdRenderer.php            # Clean production page
│   ├── JsonRenderer.php                # RFC 9457 Problem Details
│   └── PlainTextRenderer.php           # CLI output
├── Solution/
│   ├── Solution.php                    # DTO — title, description, links
│   └── SolutionLink.php               # DTO — label, url
└── Exception/
    └── FatalErrorException.php         # Wraps fatal errors

templates/
├── dev.html.php                        # Default dev page (pure HTML/CSS)
└── prod.html.php                       # Default production page

```

18 source files + 2 templates. 1,079 lines.

---

ErrorHandler (Global Registration)
----------------------------------

[](#errorhandler-global-registration)

### One-Line Setup

[](#one-line-setup)

```
use PHPdot\ErrorHandler\ErrorHandler;

ErrorHandler::register('development');
```

Registers `set_exception_handler`, `set_error_handler`, and `register_shutdown_function`. Automatically selects PlainTextRenderer for CLI, HTML renderers for web.

### Full Configuration

[](#full-configuration)

```
ErrorHandler::register('development')
    ->setLogger($psrLogger)
    ->setDevRenderer(new HtmlDevRenderer('/path/to/custom-dev.html.php'))
    ->setProdRenderer(new HtmlProdRenderer('/path/to/custom-prod.html.php'))
    ->setJsonRenderer(new JsonRenderer())
    ->addContextProvider(new QueryLogProvider($queryLogger))
    ->addContextProvider(new RouteContextProvider($router))
    ->addSolutionProvider(new ClassNotFoundSolution())
    ->addSolutionProvider(new ViewNotFoundSolution())
    ->setSensitiveKeys(['DB_PASSWORD', 'APP_KEY', 'AWS_SECRET']);
```

All methods are chainable (return `self`).

### What Gets Registered

[](#what-gets-registered)

HandlerPurpose`set_exception_handler`Catches uncaught exceptions, renders, and outputs`set_error_handler`Converts PHP warnings/notices/deprecations to `ErrorException``register_shutdown_function`Catches fatal errors (`E_ERROR`, `E_PARSE`, `E_CORE_ERROR`, `E_COMPILE_ERROR`)The error handler respects the `@` suppression operator — suppressed errors are not converted.

---

ExceptionHandler (The Core)
---------------------------

[](#exceptionhandler-the-core)

### Direct Usage

[](#direct-usage)

Use `ExceptionHandler` directly when you don't want global registration (e.g. in PSR-15 middleware):

```
use PHPdot\ErrorHandler\ExceptionHandler;
use PHPdot\ErrorHandler\Renderer\HtmlDevRenderer;
use PHPdot\ErrorHandler\Renderer\HtmlProdRenderer;
use PHPdot\ErrorHandler\Renderer\JsonRenderer;

$handler = new ExceptionHandler(
    environment: 'development',
    devRenderer: new HtmlDevRenderer(),
    prodRenderer: new HtmlProdRenderer(),
    jsonRenderer: new JsonRenderer(),
    logger: $psrLogger,
);

// Handle an exception → returns rendered string (HTML or JSON)
$output = $handler->handle($exception);

// With PSR-7 request (enables JSON detection and request tab)
$output = $handler->handle($exception, $request);
```

### Status Code Mapping

[](#status-code-mapping)

The handler determines HTTP status codes automatically:

```
$handler->getStatusCode($exception); // int
```

Exception TypeStatus CodeHas `getStatusCode()` methodUses returned value`InvalidArgumentException`400`DomainException`422`RuntimeException`500`LogicException`500`TypeError`500Everything else500Framework HTTP exceptions (e.g. `NotFoundHttpException` with `getStatusCode()` returning 404) are detected via `method_exists` — no dependency on any HTTP exception package.

### JSON Detection

[](#json-detection)

If the request has `Accept: application/json` or `Accept: application/problem+json`, the JSON renderer is used automatically:

```
// Returns HTML (no request or HTML accept)
$handler->handle($exception);

// Returns RFC 9457 JSON
$handler->handle($exception, $jsonRequest);
```

---

Renderers
---------

[](#renderers)

All renderers implement `RendererInterface` — a single method:

```
interface RendererInterface
{
    public function render(ErrorContext $context): string;
}
```

### HtmlDevRenderer

[](#htmldevrenderer)

Beautiful debug page with code snippets, stack trace, tabs, solutions. Uses a PHP template.

```
use PHPdot\ErrorHandler\Renderer\HtmlDevRenderer;

// Default template
$renderer = new HtmlDevRenderer();

// Custom template
$renderer = new HtmlDevRenderer('/path/to/my-dev-page.html.php');
```

### HtmlProdRenderer

[](#htmlprodrenderer)

Clean error page. No internals exposed. User-friendly messages.

```
use PHPdot\ErrorHandler\Renderer\HtmlProdRenderer;

$renderer = new HtmlProdRenderer();
$renderer = new HtmlProdRenderer('/path/to/my-error-page.html.php');
```

### JsonRenderer (RFC 9457)

[](#jsonrenderer-rfc-9457)

Returns [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457) JSON.

**Production output (safe):**

```
{
    "type": "about:blank",
    "title": "Not Found",
    "status": 404,
    "detail": "The requested resource was not found."
}
```

**Development output (full debug info):**

```
{
    "type": "about:blank",
    "title": "Internal Server Error",
    "status": 500,
    "detail": "Class 'App\\Service\\UserService' not found",
    "exception": {
        "class": "Error",
        "message": "Class 'App\\Service\\UserService' not found",
        "file": "/app/src/Controller/UserController.php",
        "line": 15,
        "trace": [
            {"file": "/app/src/Controller/UserController.php", "line": 15, "class": null, "function": null},
            {"file": "/app/vendor/framework/router.php", "line": 42, "class": "Router", "function": "dispatch"}
        ]
    },
    "solutions": [
        {
            "title": "Class 'UserService' not found",
            "description": "Check that the class exists and run 'composer dump-autoload'."
        }
    ]
}
```

### PlainTextRenderer

[](#plaintextrenderer)

CLI-friendly output. Used automatically when `PHP_SAPI === 'cli'`.

```
[RuntimeException] Connection refused in /app/src/Database.php:42

Stack trace:
#0 /app/src/Database.php:42 Database::connect()
#1 /app/src/App.php:15 App::boot()
#2 /app/public/index.php:8 {main}()

Suggested solutions:
  - Database not running: Start your database server and verify the connection settings.

```

### Custom Renderers

[](#custom-renderers)

Build your own renderer — implement `RendererInterface`:

```
final class TwigRenderer implements RendererInterface
{
    public function __construct(
        private readonly \Twig\Environment $twig,
    ) {}

    public function render(ErrorContext $context): string
    {
        return $this->twig->render('error.html.twig', [
            'exception' => $context->exception,
            'statusCode' => $context->statusCode,
            'stackTrace' => $context->stackTrace,
            'solutions' => $context->solutions,
        ]);
    }
}

ErrorHandler::register('development')
    ->setDevRenderer(new TwigRenderer($twig));
```

---

Dev Debug Page
--------------

[](#dev-debug-page)

### Dev Page Features

[](#dev-page-features)

The default dev template (`templates/dev.html.php`) is pure HTML/CSS with minimal vanilla JS (~15 lines for tab switching and frame collapsing). No build step. No JS framework.

- **Exception header** — status code badge, class name, message, file:line
- **Solutions panel** — suggested fixes with documentation links (green cards)
- **Stack trace** — collapsible frames with code snippets
    - Application frames highlighted, vendor frames dimmed
    - Error line highlighted in red
    - Line numbers in gutter
- **Request tab** — method, URI, headers (when PSR-7 request provided)
- **Environment tab** — server variables with sensitive keys masked
- **Context tabs** — one tab per registered ContextProvider
- **Dark/light mode** — CSS `prefers-color-scheme` (automatic)
- **Responsive** — works on mobile

### Customizing the Template

[](#customizing-the-template)

Replace the entire dev page — the template receives `$errorContext` (ErrorContext DTO):

```
ErrorHandler::register('development')
    ->setDevRenderer(new HtmlDevRenderer('/path/to/my-dev-page.html.php'));
```

Your template has access to all data:

```

Status:

    :
