Skip to content

Error Handling

Introduction

Lighthouse converts all exceptions to HTTP responses. In debug mode, you get detailed stack traces. In production, users see friendly error pages.

HTTP Exceptions

Throw HTTP exceptions for expected errors:

php
use Lighthouse\ErrorHandler\Exception\NotFoundException;
use Lighthouse\ErrorHandler\Exception\ForbiddenException;
use Lighthouse\ErrorHandler\Exception\BadRequestException;

// 404 Not Found
throw new NotFoundException('User not found');

// 403 Forbidden
throw new ForbiddenException('Access denied');

// 400 Bad Request
throw new BadRequestException('Invalid email format');

// 401 Unauthorized
throw new UnauthorizedException('Please log in');

// 405 Method Not Allowed
throw new MethodNotAllowedException(['GET', 'POST']);

Available Exceptions

ExceptionStatus Code
BadRequestException400
UnauthorizedException401
ForbiddenException403
NotFoundException404
MethodNotAllowedException405
HttpExceptionCustom

Custom Status Codes

Use HttpException for other status codes:

php
use Lighthouse\ErrorHandler\Exception\HttpException;

// 418 I'm a teapot
throw new HttpException("I'm a teapot", 418);

// 503 Service Unavailable
throw new HttpException('Service temporarily unavailable', 503);

// With custom headers
throw new HttpException('Rate limited', 429, [
    'Retry-After' => '60'
]);

Debug Mode

Enable in development:

php
$app = new Application(
    basePath: dirname(__DIR__),
    debug: true  // ← Enable debug mode
);

Debug error pages show:

  • Exception class and message
  • File and line number
  • Full stack trace
  • Request information

Production Mode

Disable for production:

php
$app = new Application(
    basePath: dirname(__DIR__),
    debug: false  // ← Disable for production
);

Production error pages show:

  • Status code
  • Generic message
  • No sensitive information

JSON Errors

When the request accepts JSON (Accept: application/json), errors return JSON:

json
{
    "error": {
        "status": 404,
        "message": "User not found"
    }
}

In debug mode:

json
{
    "error": {
        "status": 404,
        "message": "User not found",
        "type": "Lighthouse\\ErrorHandler\\Exception\\NotFoundException",
        "file": "/app/src/Controllers/UserController.php",
        "line": 42,
        "trace": [...]
    }
}

Error Logging

Add a logger to the error middleware:

php
use Lighthouse\ErrorHandler\ErrorMiddleware;

$errorMiddleware = new ErrorMiddleware(
    $app->getErrorHandler(),
    function (Throwable $e, ServerRequestInterface $request) use ($logger) {
        $logger->error($e->getMessage(), [
            'exception' => $e,
            'uri' => (string) $request->getUri(),
            'method' => $request->getMethod(),
        ]);
    }
);

Custom Error Handlers

Access the error handler:

php
$errorHandler = $app->getErrorHandler();

// Toggle debug mode
$errorHandler->setDebug(false);

Custom Renderers

Create custom error renderers:

php
use Lighthouse\ErrorHandler\Renderer\RendererInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

class XmlRenderer implements RendererInterface
{
    public function render(Throwable $e, ServerRequestInterface $request): string
    {
        return sprintf(
            '<?xml version="1.0"?><error><message>%s</message></error>',
            htmlspecialchars($e->getMessage())
        );
    }
}

$errorHandler->registerRenderer('application/xml', new XmlRenderer());

Practical Examples

Not Found in Controller

php
public function show(string $id): ResponseInterface
{
    $user = $this->users->find($id);

    if (!$user) {
        throw new NotFoundException("User {$id} not found");
    }

    return $this->json($user);
}

Authorization Check

php
public function update(ServerRequestInterface $request, string $id): ResponseInterface
{
    $user = $this->users->find($id);
    $currentUser = $request->getAttribute('user');

    if ($user->id !== $currentUser->id) {
        throw new ForbiddenException('You can only edit your own profile');
    }

    // Update user...
}

Validation

php
public function store(ServerRequestInterface $request): ResponseInterface
{
    $data = $request->getParsedBody();

    if (empty($data['email'])) {
        throw new BadRequestException('Email is required');
    }

    if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
        throw new BadRequestException('Invalid email format');
    }

    // Create user...
}

Route Exceptions

The router throws exceptions automatically:

php
// No matching route
Lighthouse\Router\Exception\RouteNotFoundException 404

// Wrong HTTP method
Lighthouse\Router\Exception\MethodNotAllowedException 405

These are caught and converted to proper HTTP error responses.

Released under the MIT License.