Passer au contenu principal

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 :
// 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.
# Développement
APP_DEBUG=true

# Production
APP_DEBUG=false
En production, APP_DEBUG doit être false. Sinon, des informations sensibles peuvent être exposées.

Rapport d’exceptions

Rapporter, c’est journaliser une exception ou l’envoyer à Sentry, Flare, 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.
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.
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();
})

Helper report()

Pour rapporter sans afficher de page d’erreur :
public function isValid(string $value): bool
{
    try {
        // Validation...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}
report() journalise sans interrompre la réponse à l’utilisateur. Utile dans les jobs en arrière-plan et pour des exceptions non critiques.

Éviter les doublons

Une même instance rapportée plusieurs fois génère des doublons. dontReportDuplicates() limite au premier appel.
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportDuplicates();
})
$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.
->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

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.
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.
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 :
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é

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é.
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.
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.
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

1

Générer la classe

php artisan make:exception InvalidOrderException
2

Implémenter `report()` et `render()`

<?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);
    }
}
report() accepte les type-hints pour injection. Le conteneur les résout automatiquement.

Interface ShouldntReport

Pour les exceptions à ne jamais rapporter, implémentez ShouldntReport.
<?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ù.
// 404
abort(404);

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

abort_if() / abort_unless()

// abort si $condition est true
abort_if(! $user->isAdmin(), 403);

// abort si $condition est false
abort_unless($user->hasPermission('edit'), 403, 'Permission denied.');
Utile pour les contrôles de droits dans les contrôleurs et middlewares. Souvent combiné avec Gates et Policies.

Contrôle global

Ignorer un type d’exception

dontReport ignore certaines exceptions au rapport. La logique de rendu reste active.
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})
Sous condition avec dontReportWhen :
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportWhen(function (Throwable $e) {
        return $e instanceof PodcastProcessingException &&
               $e->reason() === 'Subscription expired';
    });
})
Laravel ignore par défaut certaines exceptions comme les erreurs 404, l’échec CSRF (419) ou l’incohérence d’origine (403).

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

Utilisez stopIgnoring.
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.
{{-- 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 :
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
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.

Exemple : handler d’exceptions API

Pour une application API, retournez systématiquement du JSON.
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

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

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

MéthodeRô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 ShouldntReportException jamais rapportée
MéthodeRô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
  • 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
  • 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
Dernière modification le 13 juillet 2026