> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Foutafhandeling

> Leer hoe exception-afhandeling in Laravel werkt. Uitgebreide uitleg over het rapporteren en renderen van exceptions, eigen foutpagina's en het genereren van HTTP-foutresponses.

## Overzicht

Wanneer je een Laravel-project aanmaakt, is de afhandeling van fouten en exceptions al voor je geconfigureerd.
Aanpassen doe je met de `withExceptions`-methode in `bootstrap/app.php`.

### De exception-afhandelingsflow

Zo verloopt de stroom van het optreden van een exception tot de response die naar de client wordt teruggestuurd.

```mermaid theme={null}
flowchart TD
    A["Exception treedt op"] --> B["ExceptionHandler::report"]
    B --> C{"Loggen?"}
    C -->|"Yes"| D["Loggen"]
    C -->|"No"| E["Overslaan"]
    D --> F["ExceptionHandler::render"]
    E --> F
    F --> G{"Requesttype"}
    G -->|"Web"| H["HTML-foutpagina"]
    G -->|"API/JSON"| I["JSON-foutresponse"]
    H --> J["Teruggeven aan de client"]
    I --> J
```

```php theme={null}
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions): void {
        // Configureer hier het rapporteren en renderen van exceptions
    })->create();
```

Het `$exceptions`-object dat aan de `withExceptions`-closure wordt doorgegeven, is een instantie van `Illuminate\Foundation\Configuration\Exceptions` en beheert de exception-afhandeling van je hele applicatie.

### Debugconfiguratie

De `debug`-optie in `config/app.php` bepaalt hoeveel foutinformatie wordt getoond.
Standaard wordt de waarde van de omgevingsvariabele `APP_DEBUG` uit `.env` gebruikt.

```ini theme={null}
# Lokale ontwikkeling
APP_DEBUG=true

# Productie
APP_DEBUG=false
```

<Warning>
  Zet `APP_DEBUG` in productie altijd op `false`. Als je hem op `true` laat staan, loop je het risico dat vertrouwelijke informatie aan eindgebruikers wordt blootgesteld.
</Warning>

## Exceptions rapporteren

Het rapporteren van exceptions houdt in dat je exceptions logt of doorstuurt naar externe services zoals [Laravel Nightwatch](https://nightwatch.laravel.com), [Sentry](https://github.com/getsentry/sentry-laravel) of [Flare](https://flareapp.io).
Standaard worden ze gelogd op basis van de configuratie in `config/logging.php`.

### Eigen rapportagecallbacks

Wil je per soort exception een andere rapportagelogica, geef dan een closure door aan de `report`-methode.
Laravel bepaalt het soort exception op basis van de type-hint van de closure.

```php theme={null}
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // Notificatie naar externe services, enz.
    });
})
```

Ook na het registreren van een eigen callback blijft de standaardlogging actief.
Wil je de doorgifte naar de standaardafhandeling stoppen, roep dan `stop()` aan of geef `false` terug.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();
})
```

### De `report()`-helper

Wil je alleen een exception rapporteren zonder een foutpagina te tonen, gebruik dan de `report()`-helper.

```php theme={null}
public function isValid(string $value): bool
{
    try {
        // Validatielogica...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}
```

<Tip>
  Met de `report()`-helper kun je een fout loggen zonder de response naar de gebruiker te onderbreken. Handig voor exception-afhandeling in achtergrondjobs en niet-kritieke processen.
</Tip>

### Dubbele rapportages voorkomen

Als dezelfde exception-instantie meerdere keren aan `report()` wordt doorgegeven, kunnen er dubbele logregels ontstaan.
Met `dontReportDuplicates()` wordt dezelfde instantie alleen de eerste keer gelogd.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportDuplicates();
})
```

```php theme={null}
$original = new RuntimeException('Whoops!');

report($original); // Wordt gelogd

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // Wordt genegeerd (dezelfde instantie)
}
```

### Globale logcontext

Wil je aan alle exceptionlogs gemeenschappelijke informatie toevoegen, gebruik dan de `context`-methode.
Indien beschikbaar wordt het ID van de huidige gebruiker automatisch toegevoegd.

```php theme={null}
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->context(fn () => [
        'app_version' => config('app.version'),
    ]);
})
```

### Een `context()`-methode toevoegen aan een exceptionklasse

Als je op de exceptionklasse zelf een `context()`-methode definieert, kun je contextinformatie specifiek voor die exception in de logs opnemen.

```php theme={null}
<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    public function __construct(
        private readonly int $orderId,
        string $message = '',
    ) {
        parent::__construct($message);
    }

    /**
     * Geef de contextinformatie van de exception terug
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}
```

### Het logniveau wijzigen

Wil je een specifieke exception op een specifiek logniveau loggen, gebruik dan de `level`-methode.

```php theme={null}
use PDOException;
use Psr\Log\LogLevel;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->level(PDOException::class, LogLevel::CRITICAL);
})
```

### Exception-rapportages throttlen

Als er grote aantallen exceptions optreden, kun je met de `throttle`-methode het aantal rapportages beperken.

```php theme={null}
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    // Slechts 1 op de 1000 keer willekeurig rapporteren
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})
```

Wil je begrenzen op het aantal per minuut, gebruik dan `Limit`.

```php theme={null}
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof BroadcastException) {
            return Limit::perMinute(300);
        }
    });
})
```

## Exceptions renderen

Renderen is het omzetten van een exception naar een HTTP-response.
Standaard genereert Laravel automatisch een passende response, maar je kunt dit aanpassen.

### Eigen rendercallbacks

Geef een closure door aan de `render`-methode om een exception naar een response om te zetten.

```php theme={null}
use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (InvalidOrderException $e, Request $request) {
        return response()->view('errors.invalid-order', status: 500);
    });
})
```

Je kunt ook het renderen van ingebouwde exceptions (zoals `NotFoundHttpException`) overschrijven.
Als de closure geen waarde teruggeeft, wordt de standaardrendering gebruikt.

```php theme={null}
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.',
            ], 404);
        }
        // Als je null teruggeeft, wordt de standaard 404-pagina getoond
    });
})
```

### Automatische JSON/HTML-detectie

Laravel bepaalt automatisch of het HTML of JSON teruggeeft op basis van de `Accept`-header van het request.
Wil je deze beslislogica aanpassen, gebruik dan `shouldRenderJsonWhen`.

```php theme={null}
use Illuminate\Http\Request;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
        if ($request->is('admin/*')) {
            return true; // Het beheerderspaneel krijgt altijd JSON terug
        }

        return $request->expectsJson();
    });
})
```

### De volledige response aanpassen

Met de `respond`-methode kun je de gegenereerde response verder bewerken.

```php theme={null}
use Symfony\Component\HttpFoundation\Response;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->respond(function (Response $response) {
        if ($response->getStatusCode() === 419) {
            return back()->with([
                'message' => 'De pagina is verlopen. Probeer het opnieuw.',
            ]);
        }

        return $response;
    });
})
```

## Eigen exceptionklassen

Je kunt eigen exceptionklassen maken in de map `app/Exceptions/`.
Als je de methoden `report()` en `render()` definieert, worden ze automatisch aangeroepen zonder dat je configuratie in `bootstrap/app.php` hoeft te schrijven.

### Een exceptionklasse maken

<Steps>
  <Step title="Maak de exceptionklasse aan">
    ```shell theme={null}
    php artisan make:exception InvalidOrderException
    ```
  </Step>

  <Step title="Implementeer report() en render()">
    ```php theme={null}
    <?php

    namespace App\Exceptions;

    use Exception;
    use Illuminate\Http\Request;
    use Illuminate\Http\Response;

    class InvalidOrderException extends Exception
    {
        public function __construct(
            private readonly int $orderId,
            string $message = 'Invalid order.',
        ) {
            parent::__construct($message);
        }

        /**
         * Rapporteer de exception
         */
        public function report(): void
        {
            // Notificatie naar externe services, enz.
        }

        /**
         * Zet de exception om naar een HTTP-response
         */
        public function render(Request $request): Response
        {
            return response()->view('errors.invalid-order', [
                'orderId' => $this->orderId,
            ], 422);
        }
    }
    ```
  </Step>
</Steps>

<Info>
  In de `report()`-methode kun je dependency injection via type-hints gebruiken. De servicecontainer van Laravel lost ze automatisch op.
</Info>

### De `ShouldntReport`-interface

Voor exceptions die geen rapportage nodig hebben, implementeer je de `ShouldntReport`-interface.
Exceptions die deze interface implementeren, worden nooit gerapporteerd.

```php theme={null}
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}
```

## Exceptions gooien

### De `abort()`-helper

Vanaf elke plek in je applicatie kun je een HTTP-foutresponse laten optreden.

```mermaid theme={null}
flowchart LR
    A["abort(404)"] --> B["NotFoundHttpException"]
    C["abort(403)"] --> D["AccessDeniedHttpException"]
    E["abort(500)"] --> F["HttpException<br>500"]
    G["abort(422)"] --> H["UnprocessableEntityHttpException"]
    B --> I["resources/views/errors/404.blade.php"]
    D --> J["resources/views/errors/403.blade.php"]
    F --> K["resources/views/errors/500.blade.php"]
    H --> L["resources/views/errors/422.blade.php"]
```

```php theme={null}
// 404 Not Found
abort(404);

// Met bericht
abort(403, 'Je bent niet gemachtigd om deze actie uit te voeren.');
```

### `abort_if()` / `abort_unless()`

Helpers om voorwaardelijk een exception te gooien.

```php theme={null}
// Abort wanneer $condition true is
abort_if(! $user->isAdmin(), 403);

// Abort wanneer $condition false is
abort_unless($user->hasPermission('edit'), 403, 'Permission denied.');
```

<Tip>
  Handig bij permissiecontroles in controllers en middleware. Wordt ook vaak gebruikt in combinatie met gates en policies.
</Tip>

## Exceptions globaal beheren

### Specifieke exceptions negeren

Met `dontReport` geef je exceptions op die niet gerapporteerd worden. Eigen renderlogica blijft wel gewoon werken.

```php theme={null}
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})
```

Wil je voorwaardelijk negeren, geef dan een closure door aan `dontReportWhen`.

```php theme={null}
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportWhen(function (Throwable $e) {
        return $e instanceof PodcastProcessingException &&
               $e->reason() === 'Subscription expired';
    });
})
```

<Info>
  Laravel negeert standaard automatisch een aantal exceptions, zoals 404-fouten, ongeldige CSRF-tokens (419) en niet-overeenkomende origins (403).
</Info>

### Door Laravel genegeerde exceptions weer inschakelen

Om exceptions die standaard worden genegeerd weer te laten rapporteren, gebruik je `stopIgnoring`.

```php theme={null}
use Symfony\Component\HttpKernel\Exception\HttpException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->stopIgnoring(HttpException::class);
})
```

## HTTP-foutpagina's

In Laravel kun je per HTTP-statuscode eigen foutviews definiëren.

### Eigen foutviews maken

Maak in de map `resources/views/errors/` Blade-templates aan met de statuscode als bestandsnaam.

```
resources/
└── views/
    └── errors/
        ├── 404.blade.php
        ├── 403.blade.php
        └── 500.blade.php
```

Binnen de view heb je via de variabele `$exception` toegang tot de foutinformatie.

```blade theme={null}
{{-- resources/views/errors/404.blade.php --}}
<!DOCTYPE html>
<html lang="nl">
<head>
    <meta charset="UTF-8">
    <title>Pagina niet gevonden</title>
</head>
<body>
    <h1>404 - Pagina niet gevonden</h1>
    <p>{{ $exception->getMessage() }}</p>
    <a href="{{ url('/') }}">Terug naar de startpagina</a>
</body>
</html>
```

### De standaardfouttemplates publiceren

Wil je de standaardfoutpagina's van Laravel als startpunt voor aanpassing gebruiken, haal ze dan op met `vendor:publish`.

```shell theme={null}
php artisan vendor:publish --tag=laravel-errors
```

### Fallback-foutpagina's

Als fallback voor statuscodes zonder bijbehorende view kun je `4xx.blade.php` en `5xx.blade.php` aanmaken.

```
resources/
└── views/
    └── errors/
        ├── 4xx.blade.php  # Fallback voor de 400-reeks
        └── 5xx.blade.php  # Fallback voor de 500-reeks
```

<Warning>
  Voor `404`, `500` en `503` heeft Laravel standaardfoutpagina's. Om deze aan te passen, maak je aparte bestanden (zoals `404.blade.php`) in plaats van fallbacks.
</Warning>

## Praktijkvoorbeeld: een API-exceptionhandler

In applicaties die een API aanbieden, moeten exceptions altijd als JSON worden teruggegeven.
Hieronder een implementatievoorbeeld waarbij API-fouten centraal worden beheerd in `bootstrap/app.php`.

```php theme={null}
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions): void {
    // API-requests krijgen altijd JSON terug
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Resource niet gevonden.',
            ], 404);
        }
    });

    $exceptions->render(function (AuthenticationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Authenticatie vereist.',
            ], 401);
        }
    });

    $exceptions->render(function (ValidationException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Validatiefout.',
                'errors'  => $e->errors(),
            ], 422);
        }
    });
})
```

### Een eigen API-exceptionklasse implementeren

Met een basisexceptionklasse speciaal voor je API kun je op elk endpoint een uniforme foutresponse teruggeven.

```php theme={null}
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class ApiException extends Exception
{
    public function __construct(
        string $message = 'An error occurred.',
        private readonly int $statusCode = 500,
        private readonly array $errors = [],
    ) {
        parent::__construct($message);
    }

    public function render(Request $request): JsonResponse
    {
        $data = ['message' => $this->getMessage()];

        if (! empty($this->errors)) {
            $data['errors'] = $this->errors;
        }

        return response()->json($data, $this->statusCode);
    }
}
```

Een gebruiksvoorbeeld in een controller:

```php theme={null}
<?php

namespace App\Http\Controllers\Api;

use App\Exceptions\ApiException;
use App\Models\Order;

class OrderController extends Controller
{
    public function show(int $id): JsonResponse
    {
        $order = Order::find($id);

        if (! $order) {
            throw new ApiException('Bestelling niet gevonden.', 404);
        }

        if ($order->isCancelled()) {
            throw new ApiException('Deze bestelling is al geannuleerd.', 422);
        }

        return response()->json($order);
    }
}
```

## Samenvatting

<AccordionGroup>
  <Accordion title="Samenvatting van exception-rapportage">
    | Methode                       | Gebruik                                                           |
    | ----------------------------- | ----------------------------------------------------------------- |
    | `$exceptions->report()`       | Eigen rapportagelogica per soort exception registreren            |
    | `$exceptions->context()`      | Gemeenschappelijke informatie toevoegen aan alle exceptionlogs    |
    | De `context()`-methode        | Contextinformatie op de exceptionklasse zelf                      |
    | De `report()`-helper          | Alleen de exception rapporteren zonder de response te onderbreken |
    | `dontReportDuplicates()`      | Dubbele rapportages van dezelfde instantie voorkomen              |
    | De `ShouldntReport`-interface | Exceptionklassen maken die nooit worden gerapporteerd             |
  </Accordion>

  <Accordion title="Samenvatting van exception-rendering">
    | Methode                  | Gebruik                                        |
    | ------------------------ | ---------------------------------------------- |
    | `$exceptions->render()`  | Eigen responses teruggeven per soort exception |
    | De `render()`-methode    | Renderlogica op de exceptionklasse zelf        |
    | `shouldRenderJsonWhen()` | De JSON/HTML-beslislogica aanpassen            |
    | `respond()`              | De gegenereerde response verder bewerken       |
  </Accordion>

  <Accordion title="Samenvatting van HTTP-foutpagina's">
    * Bestanden zoals `resources/views/errors/404.blade.php` worden automatisch gebruikt zodra je ze aanmaakt
    * Via de variabele `$exception` heb je toegang tot de foutdetails
    * Met `php artisan vendor:publish --tag=laravel-errors` haal je de standaardtemplates op
    * Met `4xx.blade.php` / `5xx.blade.php` definieer je fallback-pagina's
  </Accordion>

  <Accordion title="Best practices voor productie">
    * Zet altijd `APP_DEBUG=false` en toon stacktraces niet aan gebruikers
    * Koppel externe fouttrackingservices zoals Sentry of Flare om fouten centraal te beheren
    * Gebruik `throttle()` om logoverstroming te voorkomen bij grote aantallen exceptions
    * Handhaaf een consistent JSON-foutresponseformaat op API-endpoints
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel Fetch Metadata](/nl/packages/laravel-fetch-metadata.md)
- [OAuth 2.0-authenticatie - Google Sheets API for Laravel](/nl/packages/laravel-google-sheets/oauth.md)
- [Laravel Pulse](/nl/pulse.md)
- [Laravel Pennant](/nl/pennant.md)
- [Session hooks](/nl/packages/laravel-copilot-sdk/hooks.md)
