Menu

Eccezioni in PHP: throw e classi di eccezione personalizzate

In PHP lanci un'eccezione con throw new Exception('message');, e chi chiama la legge con $e->getMessage() in un blocco catch. Impara le classi di eccezione predefinite, come scrivere eccezioni personalizzate, getCode(), getLine() e getPrevious(), throw come espressione e gli errori che PHP lancia da solo come ValueError e TypeError.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

In PHP lanci un'eccezione con throw new Exception('message');. L'esecuzione si ferma in quella riga e salta al blocco catch più vicino che accetta il tipo dell'eccezione, dove $e->getMessage() restituisce il messaggio. Usa una classe specifica, come InvalidArgumentException, così chi chiama può distinguere i problemi.

Un'eccezione è un oggetto. throw la passa su per lo stack delle chiamate fino al primo chiamante con un catch corrispondente, e quel chiamante decide cosa fare; la funzione che ha trovato il problema non deve saperlo. Il meccanismo della cattura, finally e la cattura di più tipi sono nella pagina su try catch.

Cosa contiene un oggetto eccezione

Ogni eccezione porta con sé un messaggio, un codice intero, il file e la riga in cui è stata creata e uno stack trace. Il costruttore accetta ($message, $code, $previous), tutti facoltativi:

getLine() è la riga di throw new, non quella del catch. Il trace elenca le chiamate che hanno portato lì (#0 /home/index.php(8): findOrder()), ed è di solito la prima cosa da leggere quando un errore compare in un log. Convertire l'eccezione in stringa (echo $e; o (string) $e) stampa tutto nel formato standard di PHP.

Classi di eccezione predefinite

PHP include una famiglia di classi di eccezione nella sua Standard PHP Library (SPL). Lanciare quella che dà il nome al problema rende il codice più leggibile e permette ai chiamanti di catturare in modo mirato. Tutte queste estendono Exception:

ClasseLanciala quando
InvalidArgumentExceptionun argomento ha la forma sbagliata: un nome vuoto, un'opzione sconosciuta
DomainExceptionun valore è fuori dall'insieme che ha senso: un prezzo negativo
OutOfRangeExceptionil codice ha chiesto un indice che non può mai esistere (un bug del chiamante)
OutOfBoundsExceptionuna chiave o un indice manca in dati noti solo a runtime
RangeExceptionun risultato calcolato esce dall'intervallo valido durante l'esecuzione
LengthExceptionqualcosa è troppo lungo o troppo corto
RuntimeExceptionun problema rilevabile solo durante l'esecuzione: un disco pieno, un timeout
UnexpectedValueExceptionuna funzione ha restituito o ricevuto un valore di un tipo inatteso
LogicExceptionè sbagliato il codice stesso, un bug piuttosto che un input errato
JsonExceptionlanciata da json_encode()/json_decode() con JSON_THROW_ON_ERROR

InvalidArgumentException, DomainException, LengthException e OutOfRangeException estendono LogicException; OutOfBoundsException, RangeException e UnexpectedValueException estendono RuntimeException. Puoi verificare tu stesso i padri di qualsiasi classe:

TypeError ha come padre Error, non Exception: appartiene alla seconda famiglia, qui sotto.

Gli errori che PHP lancia da solo

Da PHP 7, e molto di più da PHP 8, le funzioni e gli operatori di PHP lanciano sottoclassi di Error per problemi che prima erano warning. Le catturi allo stesso modo, ma catch (Exception $e) non le intercetta:

DivisionByZeroError, TypeError, ValueError, ArgumentCountError, UnhandledMatchError e il semplice Error estendono tutti Error. Ciascuno indica più spesso un bug nel codice chiamante che dati errati, ed è per questo che vivono fuori dall'albero di Exception. È declare(strict_types=1) a rendere str_repeat(5, 2) un TypeError; senza, PHP convertirebbe 5 in "5".

Scrivere una classe di eccezione personalizzata

Spesso basta una sottoclasse di una riga: il nome della classe porta già il significato, e i chiamanti possono catturare esattamente quel tipo. Quando chi cattura ha bisogno di dati (quale ordine, quale importo), aggiungi proprietà e passa il messaggio in su con parent::__construct():

Scegli il padre con attenzione: estendere RuntimeException (invece del semplice Exception) significa che il codice che cattura RuntimeException gestisce anche la tua. Una piccola gerarchia, un'eccezione base per libreria o modulo con sottoclassi specifiche sotto, permette ai chiamanti di scegliere tra "cattura tutto ciò che viene dai pagamenti" e "cattura solo questo caso".

Concatenare le eccezioni con previous

Quando catturi un'eccezione di basso livello e ne lanci una di livello più alto, passa l'originale come terzo argomento. Nulla va perso: getPrevious() risale fino alla causa principale.

L'utente vede "customers.csv could not be imported"; il log, percorrendo la catena, riceve anche "row 2 has 2 columns". Se un'eccezione concatenata non viene mai catturata, l'errore fatale di PHP stampa l'intera catena: prima la causa originale, poi ogni involucro sotto una riga Next.

throw come espressione

Da PHP 8.0, throw è un'espressione, quindi può stare ovunque sia atteso un valore: dopo ??, in un ternario, in una arrow function. Rende "prendi questo o fallisci" una sola riga:

Quando non lanciare

Le eccezioni servono per le situazioni che la funzione corrente non può gestire e che il flusso normale non prevede. Una ricerca che non trova nulla, un campo facoltativo vuoto o un utente non autenticato sono esiti ordinari: restituisci null, false o un array vuoto e lascia che sia il chiamante a verificare, e tieni le eccezioni per i casi in cui andare avanti sarebbe sbagliato.

Altre due abitudini mantengono leggibile il codice con le eccezioni: non catturare mai un'eccezione solo per ignorarla (un catch (Exception $e) {} vuoto nasconde il prossimo bug reale), e lancia classi specifiche invece del semplice Exception, così un chiamante non è mai costretto a catturare tutto per gestire un solo caso.

Domande frequenti

Come lancio un'eccezione in PHP?

Crea un oggetto eccezione e lancialo: throw new InvalidArgumentException('Quantity must be positive');. L'esecuzione si ferma in quella riga e salta al blocco catch corrispondente più vicino; se non ce n'è nessuno, lo script termina con un errore fatale "Uncaught".

Come creo un'eccezione personalizzata in PHP?

Estendi Exception o una delle sue sottoclassi: class PaymentFailedException extends RuntimeException {}. Quella sola riga basta per lanciarla e catturarla con il suo tipo. Aggiungi proprietà e un costruttore quando chi cattura ha bisogno di dati in più, e chiama parent::__construct($message, $code, $previous).

Qual è la differenza tra getMessage e getCode?

getMessage() restituisce il testo passato come primo argomento del costruttore, pensato per le persone e per i log. getCode() restituisce l'intero passato come secondo argomento (0 di default), utile per i controlli automatici come associare gli errori ai codici di stato HTTP.

Quale eccezione dovrei lanciare in PHP?

Usa una classe SPL predefinita adatta al problema: InvalidArgumentException per un argomento sbagliato, DomainException per un valore fuori da ciò che è consentito, RuntimeException per fallimenti visibili solo a runtime (un disco pieno, un timeout), LogicException per errori di programmazione. Per gli errori che i chiamanti devono distinguere, crea una tua sottoclasse.

Cos'è la concatenazione delle eccezioni in PHP?

Passare l'eccezione originale come terzo argomento del costruttore quando ne lanci una nuova: throw new ImportException('Import failed', 0, $e);. La nuova eccezione dà il contesto, e $e->getPrevious() su di essa restituisce la causa originale, così nessun dettaglio va perso.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA