Errori

Introduzione

Quando inizi un nuovo progetto Laravel, la gestione degli errori e delle eccezioni è già configurata per te; tuttavia, in qualsiasi momento, puoi utilizzare il metodo withExceptions nel file bootstrap/app.php della tua applicazione per gestire come le eccezioni vengono segnalate e visualizzate dalla tua applicazione.

L’oggetto $exceptions fornito alla closure withExceptions è un’istanza di Illuminate\Foundation\Configuration\Exceptions ed è responsabile della gestione delle eccezioni nella tua applicazione. Approfondiremo questo oggetto durante tutta questa documentazione.

Configurazione

L’opzione debug nel tuo file di configurazione config/app.php determina quante informazioni su un errore vengono effettivamente mostrate all’utente. Per default, questa opzione è impostata per rispettare il valore della variabile di ambiente APP_DEBUG, che si trova nel tuo file .env.

Durante lo sviluppo locale, dovresti impostare la variabile di ambiente APP_DEBUG su true. Nell’ambiente di produzione, questo valore dovrebbe essere sempre false. Se il valore è impostato su true in produzione, rischi di esporre valori di configurazione sensibili agli utenti finali della tua applicazione.

Gestione delle Eccezioni

Reporting Exceptions

In Laravel, il reporting delle eccezioni viene utilizzato per registrare le eccezioni o inviarle a un servizio esterno come Sentry o Flare. Per impostazione predefinita, le eccezioni verranno registrate in base alla tua configurazione di logging. Tuttavia, sei libero di registrare le eccezioni come preferisci.

Se hai bisogno di gestire diversi tipi di eccezioni in modi differenti, puoi utilizzare il metodo report nelle eccezioni della tua applicazione in bootstrap/app.php per registrare una closure che verrà eseguita quando un’eccezione di un determinato tipo deve essere segnalata. Laravel determinerà il tipo di eccezione che la closure segnala esaminando l’hint di tipo della closure:

    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->report(function (InvalidOrderException $e) {
            // ...
        });
    })

Quando registri una callback personalizzata per il reporting delle eccezioni usando il metodo report, Laravel registrerà comunque l’eccezione utilizzando la configurazione di logging predefinita per l’applicazione. Se desideri interrompere la propagazione dell’eccezione allo stack di logging predefinito, puoi utilizzare il metodo stop quando definisci la tua callback di reporting oppure restituire false dalla callback:

    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->report(function (InvalidOrderException $e) {
            // ...
        })->stop();

        $exceptions->report(function (InvalidOrderException $e) {
            return false;
        });
    })

Per personalizzare il reporting delle eccezioni per una determinata eccezione, puoi anche utilizzare reportable exceptions.

Contesto Globale del Log

Se disponibile, Laravel aggiunge automaticamente l’ID dell’utente corrente a ogni messaggio di log delle eccezioni come dato contestuale. Puoi definire i tuoi dati contestuali globali utilizzando il metodo context delle eccezioni nel file bootstrap/app.php della tua applicazione. Queste informazioni saranno incluse in ogni messaggio di log delle eccezioni scritto dalla tua applicazione:

    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->context(fn () => [
            'foo' => 'bar',
        ]);
    })

Contesto del Log delle Eccezioni

Aggiungere contesto a ogni messaggio di log può essere utile, ma a volte una particolare eccezione potrebbe avere un contesto unico che desideri includere nei tuoi log. Definendo un metodo context in una delle eccezioni della tua applicazione, puoi specificare i dati rilevanti per quell’eccezione che dovrebbero essere aggiunti alla voce di log dell’eccezione:

<?php

namespace App\Exceptions;

use Exception;

class InvalidOrderException extends Exception
{
    // ...

    /**
     * Ottiene le informazioni di contesto dell'eccezione.
     *
     * @return array<string, mixed>
     */
    public function context(): array
    {
        return ['order_id' => $this->orderId];
    }
}

L’helper report

A volte potresti dover segnalare un’eccezione ma continuare a gestire la richiesta corrente. La funzione helper report ti permette di segnalare rapidamente un’eccezione senza mostrare una pagina di errore all’utente:

public function isValid(string $value): bool
{
    try {
        // Valida il valore...
    } catch (Throwable $e) {
        report($e);

        return false;
    }
}

Deduplicazione delle Eccezioni Segnalate

Se stai utilizzando la funzione report in tutta la tua applicazione, potresti occasionalmente segnalare la stessa eccezione più volte, creando voci duplicate nei tuoi log.

Se desideri assicurarti che un’unica istanza di un’eccezione venga segnalata solo una volta, puoi chiamare il metodo dontReportDuplicates nell’applicazione nel file bootstrap/app.php:

    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->dontReportDuplicates();
    })

Ora, quando l’helper report viene chiamato con la stessa istanza di un’eccezione, verrà segnalata solo la prima chiamata:

$original = new RuntimeException('Whoops!');

report($original); // segnalata

try {
    throw $original;
} catch (Throwable $caught) {
    report($caught); // ignorata
}

report($original); // ignorata
report($caught); // ignorata

Livelli di Log delle Eccezioni

Quando i messaggi vengono scritti nei log della tua applicazione, i messaggi sono registrati a un livello di log specificato, che indica la gravità o l’importanza del messaggio registrato.

Come menzionato sopra, anche quando registri una callback personalizzata per la segnalazione delle eccezioni usando il metodo report, Laravel registrerà comunque l’eccezione utilizzando la configurazione di log predefinita per l’applicazione. Tuttavia, poiché il livello di log può influenzare i canali su cui viene registrato un messaggio, potresti voler configurare il livello di log a cui vengono registrate certe eccezioni.

Per fare questo, puoi usare il metodo level per le eccezioni nel file bootstrap/app.php della tua applicazione. Questo metodo riceve il tipo di eccezione come primo argomento e il livello di log come secondo argomento:

use PDOException;
use Psr\Log\LogLevel;

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

Ignorare le Eccezioni per Tipo

Quando sviluppi la tua applicazione, ci saranno alcuni tipi di eccezioni che non vorrai mai segnalare. Per ignorare queste eccezioni, puoi utilizzare il metodo dontReport nel file bootstrap/app.php della tua applicazione. Qualsiasi classe fornita a questo metodo non sarà mai segnalata; tuttavia, potrebbero comunque avere una logica di rendering personalizzata:

use App\Exceptions\InvalidOrderException;

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

In alternativa, puoi semplicemente "marcare" una classe di eccezione con l’interfaccia Illuminate\Contracts\Debug\ShouldntReport. Quando un’eccezione è marcata con questa interfaccia, non sarà mai segnalata dal gestore delle eccezioni di Laravel:

<?php

namespace App\Exceptions;

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

class PodcastProcessingException extends Exception implements ShouldntReport
{
    //
}

Internamente, Laravel ignora già alcuni tipi di errori per te, come le eccezioni derivanti da errori HTTP 404 o risposte HTTP 419 generate da token CSRF non validi. Se desideri istruire Laravel a smettere di ignorare un determinato tipo di eccezione, puoi utilizzare il metodo stopIgnoring nel file bootstrap/app.php della tua applicazione:

use Symfony\Component\HttpKernel\Exception\HttpException;

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

Rendering Exceptions

Per impostazione predefinita, il gestore delle eccezioni di Laravel convertirà automaticamente le eccezioni in una risposta HTTP. Tuttavia, puoi registrare una closure di rendering personalizzata per eccezioni di un determinato tipo. Puoi farlo utilizzando il metodo render per le eccezioni nel file bootstrap/app.php della tua applicazione.

La closure passata al metodo render deve restituire un’istanza di Illuminate\Http\Response, che può essere generata tramite l’helper response. Laravel determinerà il tipo di eccezione che la closure gestisce esaminando il type-hint della closure:

use App\Exceptions\InvalidOrderException;
use Illuminate\Http\Request;

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

Puoi anche utilizzare il metodo render per sovrascrivere il comportamento di rendering per le eccezioni integrate di Laravel o Symfony, come NotFoundHttpException. Se la closure fornita al metodo render non restituisce un valore, verrà utilizzato il rendering predefinito delle eccezioni di Laravel:

use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->render(function (NotFoundHttpException $e, Request $request) {
        if ($request->is('api/*')) {
            return response()->json([
                'message' => 'Record not found.'
            ], 404);
        }
    });
})

Rendering Exceptions as JSON

Quando si visualizza un’eccezione, Laravel determinerà automaticamente se deve essere resa come risposta HTML o JSON in base all’intestazione Accept della richiesta. Se desideri personalizzare come Laravel decide se rendere le risposte delle eccezioni in HTML o JSON, puoi utilizzare il metodo shouldRenderJsonWhen:

    use Illuminate\Http\Request;
    use Throwable;

    ->withExceptions(function (Exceptions $exceptions) {
        $exceptions->shouldRenderJsonWhen(function (Request $request, Throwable $e) {
            if ($request->is('admin/*')) {
                return true;
            }

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

Personalizzare la Risposta alle Eccezioni

Raramente, potresti aver bisogno di personalizzare l’intera risposta HTTP generata dal gestore delle eccezioni di Laravel. Per fare questo, puoi registrare una closure di personalizzazione della risposta usando il metodo respond:

use Symfony\Component\HttpFoundation\Response;

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

        return $response;
    });
})

Eccezioni Segnalabili e Renderizzabili

Invece di definire comportamenti personalizzati per la segnalazione e la renderizzazione nel file bootstrap/app.php della tua applicazione, puoi definire i metodi report e render direttamente nelle eccezioni della tua applicazione. Quando questi metodi esistono, verranno automaticamente chiamati dal framework:

<?php

namespace App\Exceptions;

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

class InvalidOrderException extends Exception
{
    /**
     * Segnala l'eccezione.
     */
    public function report(): void
    {
        // ...
    }

    /**
     * Renderizza l'eccezione in una risposta HTTP.
     */
    public function render(Request $request): Response
    {
        return response(/* ... */);
    }
}

Se la tua eccezione estende un’eccezione che è già renderizzabile, come un’eccezione integrata di Laravel o Symfony, puoi restituire false dal metodo render dell’eccezione per renderizzare la risposta HTTP predefinita dell’eccezione:

/**
 * Renderizza l'eccezione in una risposta HTTP.
 */
public function render(Request $request): Response|bool
{
    if (/** Determina se l'eccezione necessita di una renderizzazione personalizzata */) {

        return response(/* ... */);
    }

    return false;
}

Se la tua eccezione contiene una logica di segnalazione personalizzata che è necessaria solo quando sono soddisfatte determinate condizioni, potrebbe essere necessario istruire Laravel a segnalare a volte l’eccezione utilizzando la configurazione predefinita della gestione delle eccezioni. Per fare ciò, puoi restituire false dal metodo report dell’eccezione:

/**
 * Segnala l'eccezione.
 */
public function report(): bool
{
    if (/** Determina se l'eccezione necessita di una segnalazione personalizzata */) {

        // ...

        return true;
    }

    return false;
}

Puoi indicare eventuali dipendenze necessarie del metodo report e saranno automaticamente iniettate nel metodo dal contenitore di servizi di Laravel.

Limitazione delle Eccezioni Segnalate

Se la tua applicazione segnala un numero molto elevato di eccezioni, potresti voler limitare quante eccezioni vengono effettivamente registrate o inviate al servizio di monitoraggio degli errori esterno della tua applicazione.

Per prendere un campione casuale delle eccezioni, puoi utilizzare il metodo throttle delle eccezioni nel file bootstrap/app.php della tua applicazione. Il metodo throttle riceve una closure che dovrebbe restituire un’istanza di Lottery:

use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        return Lottery::odds(1, 1000);
    });
})

È anche possibile campionare condizionatamente in base al tipo di eccezione. Se desideri campionare solo le istanze di una specifica classe di eccezioni, puoi restituire un’istanza di Lottery solo per quella classe:

use App\Exceptions\ApiMonitoringException;
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        if ($e instanceof ApiMonitoringException) {
            return Lottery::odds(1, 1000);
        }
    });
})

Puoi anche limitare il tasso delle eccezioni registrate o inviate a un servizio di monitoraggio degli errori esterno restituendo un’istanza di Limit invece di una Lottery. Questo è utile se vuoi proteggerti da improvvisi picchi di eccezioni che inondano i tuoi log, ad esempio, quando un servizio di terze parti utilizzato dalla tua applicazione è inattivo:

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

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

Per impostazione predefinita, i limiti utilizzeranno la classe dell’eccezione come chiave di limitazione del tasso. Puoi personalizzare questo specificando la tua chiave utilizzando il metodo by su Limit:

use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Throwable;

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

Naturalmente, puoi restituire una combinazione di istanze Lottery e Limit per diverse eccezioni:

use App\Exceptions\ApiMonitoringException;
use Illuminate\Broadcasting\BroadcastException;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Lottery;
use Throwable;

->withExceptions(function (Exceptions $exceptions) {
    $exceptions->throttle(function (Throwable $e) {
        return match (true) {
            $e instanceof BroadcastException => Limit::perMinute(300),
            $e instanceof ApiMonitoringException => Lottery::odds(1, 1000),
            default => Limit::none(),
        };
    });
})

Eccezioni HTTP

Alcune eccezioni rappresentano codici di errore HTTP dal server. Ad esempio, può trattarsi di un errore "pagina non trovata" (404), di un "errore di autorizzazione" (401) o anche di un errore 500 generato dallo sviluppatore. Per generare una risposta del genere da qualsiasi parte della tua applicazione, puoi usare l’helper abort:

abort(404);

Pagine di Errore HTTP Personalizzate

Laravel rende semplice mostrare pagine di errore personalizzate per diversi codici di stato HTTP. Ad esempio, per personalizzare la pagina di errore per il codice di stato HTTP 404, crea un template di vista resources/views/errors/404.blade.php. Questa vista verrà mostrata per tutti gli errori 404 generati dalla tua applicazione. Le viste in questa cartella devono essere denominate in base al codice di stato HTTP a cui si riferiscono. L’istanza Symfony\Component\HttpKernel\Exception\HttpException sollevata dalla funzione abort sarà passata alla vista come variabile $exception:

    <h2>{{ $exception->getMessage() }}</h2>

Puoi pubblicare i template predefiniti delle pagine di errore di Laravel usando il comando Artisan vendor:publish. Una volta pubblicati i template, puoi personalizzarli come preferisci:

php artisan vendor:publish --tag=laravel-errors

Pagine di Errore HTTP di Fallback

Puoi anche definire una pagina di errore di "fallback" per una determinata serie di codici di stato HTTP. Questa pagina verrà visualizzata se non esiste una pagina corrispondente per il codice di stato HTTP specifico verificatosi. Per fare ciò, definisci un template 4xx.blade.php e un template 5xx.blade.php nella directory resources/views/errors della tua applicazione.

Quando definisci le pagine di errore di fallback, queste pagine non influenzeranno le risposte di errore 404, 500 e 503 poiché Laravel dispone di pagine interne dedicate per questi codici di stato. Per personalizzare le pagine visualizzate per questi codici di stato, dovresti definire una pagina di errore personalizzata per ciascuno di essi individualmente.

Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *