Error Handling

Introduction

Error and exception handling is configured for every Framework application via the MacropaySolutions\Framework\Exceptions\Handler class. All exceptions thrown by your application are logged according to your logging configuration and rendered into HTTP responses.

Configuration

The debug option in your config/app.php configuration file controls how much information about an error is exposed in HTTP responses. By default, this option respects the APP_DEBUG environment variable stored in your .env file.

During local development, set APP_DEBUG=true. In production environments, this value must always be false. Setting APP_DEBUG=true in production risks exposing sensitive configuration values and stack traces in your API responses.

The Exception Handler

Reporting Exceptions

All unhandled exceptions are passed to the report method on your App\Exceptions\Handler class (which extends MacropaySolutions\Framework\Exceptions\Handler). By default, the exception handler writes the exception to the logger resolved from the container (Psr\Log\LoggerInterface).

To customize how an exception is logged, you may define a report method directly on that specific exception class.

The report Helper

To report an exception without interrupting request execution, call the report global helper function:

public function process(string $data): bool
{
    try {
        return $this->parseData($data);
    } catch (\Throwable $e) {
        \report($e);

        return false;
    }
}

Exception Log Context

To add custom contextual data to log entries for a specific exception, define a context method on the exception class. The returned array will automatically be passed into log records:

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    /**
     * Create a new exception instance.
     */
    public function __construct(
        protected int $orderId
    ) {
        parent::__construct('Invalid order payload.');
    }

    /**
     * Get the exception's context information for logging.
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return [
            'order_id' => $this->orderId,
        ];
    }
}

Ignoring Exceptions by Type

To prevent specific types of exceptions from being logged, list their class names in the $dontReport property on your App\Exceptions\Handler class:

namespace App\Exceptions;

use App\Exceptions\InvalidOrderException;
use MacropaySolutions\Framework\Exceptions\Handler as ExceptionHandler;

class Handler extends ExceptionHandler
{
    /**
     * A list of the exception types that should not be reported.
     *
     * @var array<int, class-string<\Throwable>>
     */
    protected $dontReport = [
        InvalidOrderException::class,
    ];
}

The base handler automatically ignores internal HTTP exceptions, validation errors, and missing model exceptions (including ModelNotFoundException and RecordsNotFoundException).

Rendering Exceptions

By default, the framework handler converts caught exceptions into an HTTP or JSON response based on the incoming request headers.

The base exception handler automatically maps internal database and security exceptions to appropriate HTTP status codes:

  • ModelNotFoundException404 Not Found
  • RecordsNotFoundException404 Not Found
  • SuspiciousOperationException404 Not Found
  • AuthorizationException403 Forbidden
  • TokenMismatchException419 Page Expired

Reportable and Renderable Exceptions

Instead of handling exception logic inside App\Exceptions\Handler, you may define report and render methods directly on custom exception classes:

namespace App\Exceptions;

use Exception;
use MacropaySolutions\Kernel\Http\Request;
use MacropaySolutions\Kernel\Http\Response;

class InvalidOrderException extends Exception
{
    /**
     * Custom report method. Returning false allows standard logging to continue.
     */
    public function report(): bool
    {
        \app('log')->warning('Order processing failed for specific customer');

        return true;
    }

    /**
     * Custom render method into an HTTP response.
     */
    public function render(Request $request): Response
    {
        return \response()->json([
            'status' => 'error',
            'message' => $this->getMessage(),
        ], 400);
    }
}

You may also implement the MacropaySolutions\Kernel\Contracts\Support\Responsable interface on your exception class to convert it into a response:

namespace App\Exceptions;

use Exception;
use MacropaySolutions\Kernel\Contracts\Support\Responsable;
use Symfony\Component\HttpFoundation\Response;

class CustomApiException extends Exception implements Responsable
{
    public function toResponse($request): Response
    {
        return \response()->json([
            'error' => $this->getMessage(),
        ], 422);
    }
}

HTTP Exceptions

The abort Helper

To throw an HTTP exception and stop processing immediately, call the abort global helper function:

\abort(404, 'The requested resource was not found.');

Passing 404 throws a Symfony\Component\HttpKernel\Exception\NotFoundHttpException. Any other HTTP status code throws a Symfony\Component\HttpKernel\Exception\HttpException.

HTTP Response Formatting

JSON API Requests

When $request->expectsJson() is true, the handler returns a structured JSON response:

With APP_DEBUG=true:

{
    "message": "The requested resource was not found.",
    "exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
    "file": "/var/www/app/Http/Controllers/PostController.php",
    "line": 42,
    "trace": [...]
}

With APP_DEBUG=false:

{
    "message": "The requested resource was not found."
}

Non-JSON Requests

When $request->expectsJson() is false and APP_DEBUG=false, the exception handler returns a lightweight, centered HTML layout containing the status code and text (e.g., <h1>404: Not Found</h1> or <h1>500: Server Error</h1>).


This site uses Just the Docs, a documentation theme for Jekyll.