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

# Gestion des erreurs

> Comment fonctionne la gestion des exceptions dans Laravel : rapport, rendu, pages d'erreur personnalisées et réponses HTTP d'erreur.

## Vue d'ensemble

Dans un projet Laravel, la gestion des erreurs et exceptions est préconfigurée.
La personnalisation se fait via `withExceptions` dans `bootstrap/app.php`.

### Flux de traitement d'une exception

De l'apparition d'une exception jusqu'à la réponse envoyée au client :

```mermaid theme={null}
flowchart TD
    A["Exception levée"] --> B["ExceptionHandler::report"]
    B --> C{"À journaliser ?"}
    C -->|"Oui"| D["Journalisation"]
    C -->|"Non"| E["Sauté"]
    D --> F["ExceptionHandler::render"]
    E --> F
    F --> G{"Type de requête"}
    G -->|"Web"| H["Page HTML"]
    G -->|"API/JSON"| I["Réponse JSON"]
    H --> J["Retour au 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 {
        // Configuration du rapport et du rendu
    })->create();
```

L'objet `$exceptions` passé à la closure est une instance de `Illuminate\Foundation\Configuration\Exceptions` qui pilote la gestion des exceptions.

### Debug

L'option `debug` de `config/app.php` contrôle le niveau d'information affiché. Par défaut, elle suit `APP_DEBUG`.

```ini theme={null}
# Développement
APP_DEBUG=true

# Production
APP_DEBUG=false
```

<Warning>
  En production, `APP_DEBUG` doit être `false`. Sinon, des informations sensibles peuvent être exposées.
</Warning>

## Rapport d'exceptions

Rapporter, c'est journaliser une exception ou l'envoyer à [Sentry](https://github.com/getsentry/sentry-laravel), [Flare](https://flareapp.io), etc.
Par défaut, elles sont journalisées selon `config/logging.php`.

### Callback de rapport personnalisé

Pour un traitement spécifique selon le type d'exception, passez une closure à `report`. Laravel utilise le type-hint pour identifier l'exception.

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

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // Notification vers un service externe, etc.
    });
})
```

Le rapport par défaut continue. Pour l'arrêter, appelez `stop()` ou retournez `false`.

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

### Helper `report()`

Pour rapporter sans afficher de page d'erreur :

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

        return false;
    }
}
```

<Tip>
  `report()` journalise sans interrompre la réponse à l'utilisateur. Utile dans les jobs en arrière-plan et pour des exceptions non critiques.
</Tip>

### Éviter les doublons

Une même instance rapportée plusieurs fois génère des doublons.
`dontReportDuplicates()` limite au premier appel.

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

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

report($original); // Journalisé

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // Ignoré (même instance)
}
```

### Contexte global de log

Ajoutez des informations communes à tous les logs d'exception avec `context`.
L'ID de l'utilisateur courant est ajouté automatiquement s'il est disponible.

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

### Méthode `context()` sur la classe d'exception

Ajoutez du contexte spécifique à une exception.

```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);
    }

    /**
     * Retourne le contexte de l'exception.
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}
```

### Niveau de log

Fixez un niveau pour un type d'exception avec `level`.

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

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

### Limitation (throttle)

Pour un grand volume d'exceptions, limitez avec `throttle`.

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

->withExceptions(function (Exceptions $exceptions): void {
    // 1 chance sur 1000 de rapporter
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})
```

Limitation par minute :

```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);
        }
    });
})
```

## Rendu d'exceptions

Le rendu convertit une exception en réponse HTTP. Laravel produit une réponse appropriée par défaut, mais vous pouvez la personnaliser.

### Callback de rendu personnalisé

```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);
    });
})
```

Vous pouvez surcharger le rendu des exceptions intégrées (`NotFoundHttpException`…).
Si la closure ne retourne rien, le rendu par défaut est utilisé.

```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);
        }
        // Retourner null → page 404 par défaut
    });
})
```

### JSON / HTML automatique

Laravel décide selon l'en-tête `Accept`. Pour personnaliser cette logique, utilisez `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; // Toujours JSON pour l'admin
        }

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

### Personnaliser la réponse finale

`respond` transforme la réponse finale.

```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' => 'La session a expiré. Veuillez réessayer.',
            ]);
        }

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

## Classes d'exception personnalisées

Créez vos exceptions dans `app/Exceptions/`.
Les méthodes `report()` et `render()` définies dans la classe sont appelées automatiquement, sans configuration dans `bootstrap/app.php`.

### Créer une exception

<Steps>
  <Step title="Générer la classe">
    ```shell theme={null}
    php artisan make:exception InvalidOrderException
    ```
  </Step>

  <Step title="Implémenter `report()` et `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);
        }

        /**
         * Rapporte l'exception.
         */
        public function report(): void
        {
            // Notification vers un service externe...
        }

        /**
         * Convertit l'exception en réponse HTTP.
         */
        public function render(Request $request): Response
        {
            return response()->view('errors.invalid-order', [
                'orderId' => $this->orderId,
            ], 422);
        }
    }
    ```
  </Step>
</Steps>

<Info>
  `report()` accepte les type-hints pour injection. Le conteneur les résout automatiquement.
</Info>

### Interface `ShouldntReport`

Pour les exceptions à ne jamais rapporter, implémentez `ShouldntReport`.

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

namespace App\Exceptions;

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

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

## Lever une exception

### Helper `abort()`

Générez une réponse HTTP d'erreur depuis n'importe où.

```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
abort(404);

// Avec message
abort(403, 'Vous n\'avez pas la permission d\'effectuer cette action.');
```

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

```php theme={null}
// abort si $condition est true
abort_if(! $user->isAdmin(), 403);

// abort si $condition est false
abort_unless($user->hasPermission('edit'), 403, 'Permission denied.');
```

<Tip>
  Utile pour les contrôles de droits dans les contrôleurs et middlewares. Souvent combiné avec Gates et Policies.
</Tip>

## Contrôle global

### Ignorer un type d'exception

`dontReport` ignore certaines exceptions au rapport. La logique de rendu reste active.

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

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

Sous condition avec `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 ignore par défaut certaines exceptions comme les erreurs 404, l'échec CSRF (419) ou l'incohérence d'origine (403).
</Info>

### Réactiver une exception ignorée par défaut

Utilisez `stopIgnoring`.

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

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

## Pages d'erreur HTTP

Définissez une vue personnalisée par code HTTP.

### Créer une vue d'erreur personnalisée

Placez un template Blade nommé par code dans `resources/views/errors/`.

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

La variable `$exception` est disponible.

```blade theme={null}
{{-- resources/views/errors/404.blade.php --}}
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Page introuvable</title>
</head>
<body>
    <h1>404 - Page introuvable</h1>
    <p>{{ $exception->getMessage() }}</p>
    <a href="{{ url('/') }}">Retour à l'accueil</a>
</body>
</html>
```

### Publier les vues par défaut

Pour partir des vues fournies par Laravel :

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

### Vue de repli

Créez `4xx.blade.php` et `5xx.blade.php` comme repli.

```
resources/
└── views/
    └── errors/
        ├── 4xx.blade.php  # Fallback 4xx
        └── 5xx.blade.php  # Fallback 5xx
```

<Warning>
  Laravel fournit des pages par défaut pour `404`, `500`, `503`. Pour les personnaliser, créez des fichiers individuels (`404.blade.php`…) plutôt que le fallback.
</Warning>

## Exemple : handler d'exceptions API

Pour une application API, retournez systématiquement du JSON.

```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 {
    // Toujours JSON pour /api
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Ressource introuvable.',
            ], 404);
        }
    });

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

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

### Classe d'exception API personnalisée

Une classe de base unifie les réponses.

```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);
    }
}
```

Exemple dans un contrôleur :

```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('Commande introuvable.', 404);
        }

        if ($order->isCancelled()) {
            throw new ApiException('Cette commande est annulée.', 422);
        }

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

## Récapitulatif

<AccordionGroup>
  <Accordion title="Rapport d'exceptions">
    | Méthode                    | Rôle                                 |
    | -------------------------- | ------------------------------------ |
    | `$exceptions->report()`    | Logique de rapport par type          |
    | `$exceptions->context()`   | Contexte commun à tous les logs      |
    | Méthode `context()`        | Contexte propre à une exception      |
    | Helper `report()`          | Rapporte sans interrompre la réponse |
    | `dontReportDuplicates()`   | Prévient les doublons                |
    | Interface `ShouldntReport` | Exception jamais rapportée           |
  </Accordion>

  <Accordion title="Rendu d'exceptions">
    | Méthode                  | Rôle                           |
    | ------------------------ | ------------------------------ |
    | `$exceptions->render()`  | Réponse personnalisée par type |
    | Méthode `render()`       | Rendu porté par l'exception    |
    | `shouldRenderJsonWhen()` | Personnalise JSON/HTML         |
    | `respond()`              | Modifie la réponse finale      |
  </Accordion>

  <Accordion title="Pages d'erreur HTTP">
    * Créer `resources/views/errors/404.blade.php` etc. suffit
    * `$exception` donne accès aux détails
    * `php artisan vendor:publish --tag=laravel-errors` publie les templates par défaut
    * `4xx.blade.php` / `5xx.blade.php` pour un repli
  </Accordion>

  <Accordion title="Bonnes pratiques en production">
    * `APP_DEBUG=false` obligatoire pour ne pas exposer les stack traces
    * Intégrer Sentry, Flare ou équivalent pour un suivi centralisé
    * Utiliser `throttle()` pour éviter de saturer les logs
    * Maintenir un format JSON d'erreur cohérent pour les API
  </Accordion>
</AccordionGroup>


## Related topics

- [Authentification OAuth 2.0 - Google Sheets API for Laravel](/fr/packages/laravel-google-sheets/oauth.md)
- [Laravel Fetch Metadata](/fr/packages/laravel-fetch-metadata.md)
- [Construire une SPA avec Inertia.js](/fr/blog/inertia-introduction.md)
- [Laravel Pennant](/fr/pennant.md)
- [Laravel Pulse](/fr/pulse.md)
