Vai al contenuto principale

Panoramica

Quando crei un progetto Laravel, la gestione di errori ed eccezioni è già preconfigurata. Personalizzi con il metodo withExceptions in bootstrap/app.php.

Flusso di gestione delle eccezioni

Dal momento in cui viene sollevata un’eccezione fino alla risposta al client.
// bootstrap/app.php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;

return Application::configure(basePath: dirname(__DIR__))
    ->withExceptions(function (Exceptions $exceptions): void {
        // Configura qui report e rendering delle eccezioni
    })->create();
L’oggetto $exceptions è un’istanza di Illuminate\Foundation\Configuration\Exceptions che gestisce l’handler dell’applicazione.

Debug

L’opzione debug di config/app.php controlla quanto viene mostrato. Di default segue APP_DEBUG in .env.
# Locale
APP_DEBUG=true

# Produzione
APP_DEBUG=false
In produzione imposta sempre APP_DEBUG a false. Lasciarlo su true espone dati sensibili agli utenti finali.

Reporting delle eccezioni

Il reporting è la registrazione delle eccezioni in log o su servizi esterni come Sentry o Flare. Di default vanno nel log secondo config/logging.php.

Callback di report personalizzate

Per gestire in modo diverso i vari tipi di eccezione, passa una closure a report. Laravel deduce il tipo dal type hint.
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // Notifica a servizi esterni, ecc.
    });
})
Anche con callback personalizzate, il log di default continua. Per fermare la propagazione chiama stop() o restituisci false.
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->report(function (InvalidOrderException $e) {
        // ...
    })->stop();
})

Helper report()

Per segnalare un’eccezione senza mostrare una pagina d’errore, usa report().
public function isValid(string $value): bool
{
    try {
        // Logica di validazione...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}
report() registra l’errore senza interrompere la risposta all’utente. Utile per background job e operazioni non critiche.

Prevenire duplicati

Se la stessa istanza viene passata più volte a report() compaiono log duplicati. Con dontReportDuplicates() viene registrata solo la prima volta.
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportDuplicates();
})
$original = new RuntimeException('Whoops!');

report($original); // Registrato

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // Ignorato (stessa istanza)
}

Contesto globale del log

Per aggiungere informazioni comuni a tutti i log di eccezioni usa context. L’ID utente corrente viene aggiunto automaticamente se disponibile.
->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->context(fn () => [
        'app_version' => config('app.version'),
    ]);
})

Metodo context() sulla classe eccezione

Definendo context() sulla classe eccezione includi informazioni specifiche di quella eccezione.
<?php

namespace App\Exceptions;

use Exception;

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

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

Livello di log

Per registrare certe eccezioni a un livello specifico usa level.
use PDOException;
use Psr\Log\LogLevel;

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

Throttling delle segnalazioni

In caso di molte eccezioni puoi limitare le segnalazioni con throttle.
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    // 1 segnalazione ogni 1000 casuali
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})
Per limite al minuto usa Limit.
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);
        }
    });
})

Rendering delle eccezioni

Il rendering trasforma l’eccezione in una risposta HTTP. Laravel lo fa in automatico, ma puoi personalizzare.

Callback di rendering personalizzate

Con una closure passata a render trasformi l’eccezione in risposta.
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);
    });
})
Puoi sovrascrivere anche il rendering di eccezioni integrate (come NotFoundHttpException). Se la closure non ritorna nulla, viene usato il rendering di default.
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);
        }
        // null → pagina 404 di default
    });
})

JSON o HTML in automatico

Laravel decide in base all’header Accept. Per personalizzare la logica usa 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;
        }

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

Manipolare la risposta finale

Con respond modifichi la risposta generata.
use Symfony\Component\HttpFoundation\Response;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->respond(function (Response $response) {
        if ($response->getStatusCode() === 419) {
            return back()->with([
                'message' => 'La pagina è scaduta. Riprova.',
            ]);
        }

        return $response;
    });
})

Classi eccezione personalizzate

Puoi creare eccezioni personalizzate in app/Exceptions/. Se definisci report() e render() vengono chiamati automaticamente senza dover configurare bootstrap/app.php.

Creare la classe eccezione

1

Crea la classe

php artisan make:exception InvalidOrderException
2

Implementa report() e 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);
    }

    public function report(): void
    {
        // Notifica a servizi esterni
    }

    public function render(Request $request): Response
    {
        return response()->view('errors.invalid-order', [
            'orderId' => $this->orderId,
        ], 422);
    }
}
In report() puoi usare la dependency injection tramite type hint. Il service container risolve automaticamente.

Interfaccia ShouldntReport

Per eccezioni che non vanno mai segnalate implementa ShouldntReport.
<?php

namespace App\Exceptions;

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

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}

Lanciare eccezioni

Helper abort()

Ovunque nell’applicazione puoi generare una risposta di errore HTTP.
// 404
abort(404);

// Con messaggio
abort(403, 'Non hai i permessi per questa operazione.');

abort_if() / abort_unless()

Helper per lanciare eccezioni condizionali.
// Abort se la condizione è vera
abort_if(! $user->isAdmin(), 403);

// Abort se la condizione è falsa
abort_unless($user->hasPermission('edit'), 403, 'Permesso negato.');
Comodi per i controlli di permesso in controller e middleware, spesso combinati con gate e policy.

Controllo globale delle eccezioni

Ignorare eccezioni

Escludi eccezioni dal reporting con dontReport. La logica di rendering personalizzata continua a funzionare.
use App\Exceptions\InvalidOrderException;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReport([
        InvalidOrderException::class,
    ]);
})
Condizionalmente con dontReportWhen.
use Throwable;

->withExceptions(function (Exceptions $exceptions): void {
    $exceptions->dontReportWhen(function (Throwable $e) {
        return $e instanceof PodcastProcessingException &&
               $e->reason() === 'Subscription expired';
    });
})
Laravel ignora già di default alcune eccezioni (404, CSRF 419, mismatch origin 403, ecc.).

Riportare eccezioni ignorate

Con stopIgnoring riporti eccezioni ignorate.
use Symfony\Component\HttpKernel\Exception\HttpException;

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

Pagine di errore HTTP

Puoi definire viste personalizzate per ogni codice HTTP.

Creare viste di errore

Crea in resources/views/errors/ template Blade con il codice HTTP come nome.
resources/
└── views/
    └── errors/
        ├── 404.blade.php
        ├── 403.blade.php
        └── 500.blade.php
Nella vista usa $exception.
{{-- resources/views/errors/404.blade.php --}}
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <title>Pagina non trovata</title>
</head>
<body>
    <h1>404 - Pagina non trovata</h1>
    <p>{{ $exception->getMessage() }}</p>
    <a href="{{ url('/') }}">Torna alla home</a>
</body>
</html>

Pubblicare i template di default

Per personalizzarli usa vendor:publish.
php artisan vendor:publish --tag=laravel-errors

Pagine di fallback

Crea 4xx.blade.php e 5xx.blade.php per fare da fallback.
resources/
└── views/
    └── errors/
        ├── 4xx.blade.php
        └── 5xx.blade.php
Per 404, 500, 503 Laravel ha pagine di default. Per personalizzarli crea i file specifici, non il fallback.

Esempio pratico: handler API

Nelle applicazioni API le eccezioni vanno restituite sempre in JSON. Esempio in bootstrap/app.php.
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
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' => 'Risorsa non trovata.',
            ], 404);
        }
    });

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

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

Eccezione API personalizzata

<?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);
    }
}
Uso nel controller:
<?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('Ordine non trovato.', 404);
        }

        if ($order->isCancelled()) {
            throw new ApiException('Ordine già annullato.', 422);
        }

        return response()->json($order);
    }
}
ModalitàUso
$exceptions->report()Callback per tipo di eccezione
$exceptions->context()Info comuni a tutti i log
Metodo context()Info specifiche della classe eccezione
Helper report()Segnala senza interrompere
dontReportDuplicates()Evita duplicati
ShouldntReportClassi mai segnalate
ModalitàUso
$exceptions->render()Response per tipo di eccezione
Metodo render()Response definita nella classe
shouldRenderJsonWhen()Personalizza JSON/HTML
respond()Manipola la response
  • Crea resources/views/errors/404.blade.php ecc. per attivarle
  • Usa $exception per i dettagli
  • vendor:publish --tag=laravel-errors per i template di default
  • 4xx.blade.php / 5xx.blade.php come fallback
  • APP_DEBUG=false sempre, per non esporre lo stack trace
  • Integra Sentry, Flare o strumenti simili
  • throttle() per non intasare i log
  • Mantieni un formato JSON coerente per gli errori delle API
Ultima modifica il 13 luglio 2026