Error Handling

Comprehensive error handling system with beautiful debug views and production-ready error pages.

PSR-3 Ready Blade Templates Debug Mode Production Safe

Overview

The Larafony error handling system captures all uncaught exceptions and PHP errors, rendering them through beautiful Blade templates. It automatically switches between detailed debug views for development and clean, user-friendly error pages for production based on the APP_DEBUG environment variable.

Key Features

Configuration

Environment Setup

Control error display mode through your .env file:

# Development: Show detailed debug traces
APP_DEBUG=true

# Production: Show user-friendly error pages
APP_DEBUG=false

Service Provider Registration

Register the ErrorHandlerServiceProvider in your bootstrap/app.php. Important: It must be registered AFTER ViewServiceProvider because it depends on ViewManager.

$app->withServiceProviders([
    ConfigServiceProvider::class,
    DatabaseServiceProvider::class,
    HttpServiceProvider::class,
    RouteServiceProvider::class,
    ViewServiceProvider::class,  // ViewManager must be registered first
    WebServiceProvider::class,
    ErrorHandlerServiceProvider::class, // Must be LAST
]);

Error Views

Create three Blade views in resources/views/blade/errors/:

404 Error Page (errors.404)

Rendered when NotFoundError exception is thrown:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>404 - Page Not Found</title>
    <style>
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            /* Beautiful purple gradient with animated stars */
        }
    </style>
</head>
<body>
    <h1>404 - Page Not Found</h1>
    <p>The page you're looking for doesn't exist.</p>
    <a href="/">Go Home</a>
</body>
</html>

500 Error Page (errors.500)

Rendered for all other exceptions in production mode:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>500 - Internal Server Error</title>
    <style>
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            /* Matching blue gradient theme */
        }
    </style>
</head>
<body>
    <h1>500 - Internal Server Error</h1>
    <p>Something went wrong on our end.</p>
    <a href="/">Go Home</a>
</body>
</html>

Debug View (errors.debug)

Rendered in debug mode with full exception details and interactive backtrace:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{ $exception['class'] }} | Debug</title>
    <!-- VS Code dark theme styling -->
</head>
<body>
    <!-- Exception details -->
    <div class="header">
        <div class="exception-class">{{ $exception['class'] }}</div>
        <div class="exception-message">{{ $exception['message'] }}</div>
        <div class="exception-location">
            {{ $exception['file'] }}:{{ $exception['line'] }}
        </div>
    </div>

    <!-- Sidebar with clickable stack frames -->
    <div class="sidebar">
        @foreach($backtrace as $index => $frame)
        <div class="frame" data-frame="{{ $index }}">
            {{ $frame['class'] }}::{{ $frame['function'] }}()
            <br>{{ $frame['file'] }}:{{ $frame['line'] }}
        </div>
        @endforeach
    </div>

    <!-- Code snippets with line numbers -->
    <div class="content">
        @foreach($backtrace as $index => $frame)
        <div id="frame-{{ $index }}">
            @foreach($frame['snippet']['lines'] as $lineNum => $lineContent)
            <div class="code-line {{ $lineNum == $frame['snippet']['errorLine'] ? 'error-line' : '' }}">
                <span class="line-number">{{ $lineNum }}</span>
                <span class="line-content">{{ $lineContent }}</span>
            </div>
            @endforeach
        </div>
        @endforeach
    </div>

    <!-- JavaScript for frame navigation -->
    <script>
        document.querySelectorAll('.frame').forEach(frame => {
            frame.addEventListener('click', function() {
                // Switch to selected frame's code
            });
        });
    </script>
</body>
</html>

Usage Examples

Throwing 404 Errors

Throw NotFoundError to trigger a 404 response:

use Larafony\Framework\Core\Exceptions\NotFoundError;

public function findForRoute(string|int $value): static
{
    $result = self::query()->where('id', '=', $value)->first();

    if ($result === null) {
        throw new NotFoundError(
            sprintf('Model %s with id %s not found', static::class, $value)
        );
    }

    return $result;
}

Custom Exception Handling

Create custom exceptions extending NotFoundError:

namespace App\Exceptions;

use Larafony\Framework\Core\Exceptions\NotFoundError;

class ResourceNotFoundException extends NotFoundError
{
    public function __construct(string $resource, string|int $id)
    {
        parent::__construct(
            sprintf('Resource "%s" with ID "%s" was not found', $resource, $id)
        );
    }
}

// Usage
throw new ResourceNotFoundException('User', 42);

Programmatic Backtrace Generation

Generate backtraces programmatically for logging or debugging:

use Larafony\Framework\ErrorHandler\Backtrace;

$backtrace = new Backtrace();

try {
    // Risky operation
    throw new \RuntimeException('Something went wrong');
} catch (\Throwable $e) {
    $trace = $backtrace->generate($e);

    foreach ($trace->frames as $frame) {
        echo $frame->file . ':' . $frame->line . PHP_EOL;
        echo $frame->class . '::' . $frame->function . '()' . PHP_EOL;

        // Access code snippet
        foreach ($frame->snippet->lines as $lineNum => $lineContent) {
            echo $lineNum . ': ' . $lineContent . PHP_EOL;
        }
    }
}

Architecture

Core Components

Modern PHP Features

The error handling system leverages PHP 8.5 features:

// Property hooks for immutable public access
class TraceCollection
{
    public private(set) array $frames;  // Public read, private write
}

// Readonly value objects
readonly class TraceFrame
{
    public function __construct(
        public string $file,
        public int $line,
        public ?string $class,
        public string $function,
        public array $args,
        public CodeSnippet $snippet
    ) {}
}

Comparison with Other Frameworks

Feature Larafony Laravel Symfony
Registration set_exception_handler() withExceptions() in bootstrap Event listener on kernel.exception
Debug Views Custom Blade templates Whoops library Symfony Profiler
Configuration ENV (APP_DEBUG) Config files + ENV YAML/PHP config
Approach Native PHP handlers + Blade Laravel exceptions + middleware Event-driven architecture
Key Difference: Larafony uses PHP's native exception handlers directly for simplicity and transparency, while Laravel and Symfony use framework-specific abstractions. This makes Larafony's error handling easier to understand and customize without deep framework knowledge.

Learn More

This implementation is explained in detail with step-by-step tutorials, tests, and best practices at masterphp.eu

Demo App: See error handling in action with custom 404/500 pages and interactive debug views. The demo application showcases production-ready error templates with beautiful gradients and full exception backtraces in development mode.

View on Packagist View on GitHub