In PHP metti il codice che potrebbe fallire dentro try { ... } e gestisci il fallimento in catch (Exception $e) { ... }. Se qualcosa nel blocco try lancia un'eccezione, PHP salta direttamente al catch corrispondente, e $e->getMessage() ti dice cosa è andato storto. Se non viene lanciato nulla, il blocco catch viene saltato.
Cambia divide(10, 0) in divide(10, 5) ed eseguilo di nuovo: nessuna eccezione, quindi viene stampato not printed e il blocco catch viene saltato. Senza try, l'eccezione terminerebbe lo script con un errore fatale "Uncaught". Come lanciare le tue eccezioni e scrivere classi di eccezione è spiegato nella pagina sulle eccezioni.
La sintassi di try catch
La forma completa ha tre parti. È obbligatorio solo try, più almeno un catch o un finally:
try {
// code that may throw
} catch (SomeException $e) {
// runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
// runs for either of these types
} finally {
// always runs, thrown or not
}
Il tipo nel catch decide cosa cattura: la classe stessa e ogni classe che la estende. Da PHP 8.0 la variabile è facoltativa quando non ti serve: catch (JsonException) { ... }.
Catturare Exception, Error o Throwable
Da PHP 7, i fallimenti che PHP rileva da solo (chiamare un metodo inesistente, un tipo di argomento sbagliato, una divisione per zero) lanciano un Error, non un'Exception, e PHP 8 ha trasformato molti ex warning in TypeError e ValueError. Entrambi implementano l'interfaccia Throwable, ma nessuno dei due estende l'altro, quindi catch (Exception $e) non cattura un Error:
Throwable
├── Exception (problems a program expects and handles)
│ ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error (engine errors, usually a bug in the code)
├── TypeError
├── ValueError
├── ArithmeticError
│ └── DivisionByZeroError
└── UnhandledMatchError, ...
Eseguilo per vedere quale catch gestisce ciascun problema:
Quindi cosa conviene catturare?
catch (SomeSpecificException $e)quando sai cosa può andare storto e cosa fare. È la maggior parte del codice.catch (Exception $e)per "qualsiasi cosa una libreria o il mio codice abbia lanciato di proposito".catch (Throwable $e)al livello più esterno (un gestore di richieste, un esecutore di job) per registrare ogni fallimento, compresi gli errori di PHP stesso, e mostrare un messaggio cortese.
Catturare più tipi di eccezione
Scrivi più blocchi catch per gestire tipi diversi in modi diversi. PHP li prova dall'alto verso il basso e usa la prima corrispondenza, quindi un tipo specifico deve venire prima del suo padre, altrimenti il padre catturerà tutto per primo. Quando più tipi ricevono lo stesso trattamento, uniscili in un solo catch con |:
Tutte e tre le classi estendono Exception, quindi le catturerebbe anche un unico catch (Exception $e), ma allora il codice non potrebbe più rispondere 404 per una e 400 per le altre.
finally: codice che viene sempre eseguito
Un blocco finally viene eseguito dopo try e catch comunque siano finiti: normalmente, con un'eccezione catturata, con un'eccezione che nessuno ha catturato, o con un return. È il posto dove rilasciare ciò che il blocco try ha acquisito: chiudere un file, rilasciare un lock, fermare un timer.
Per good.txt il file viene chiuso prima che venga stampato saved, perché finally viene eseguito prima che la funzione restituisca davvero il valore. Per bad.txt non c'è un catch dentro process(): finally chiude il file, poi l'eccezione prosegue fino al catch del chiamante.
Evita return dentro finally: sostituisce il valore restituito dal blocco try e scarta in silenzio un'eccezione che stava uscendo:
function f(): string
{
try {
return 'from try';
} finally {
return 'from finally';
}
}
function g(): string
{
try {
throw new RuntimeException('lost');
} finally {
return 'no exception reaches the caller';
}
}
echo f(); // from finally
echo g(); // no exception reaches the caller
Rilanciare o incapsulare un'eccezione
A volte un blocco catch può gestire un problema solo in parte: registrarlo, fare pulizia, poi lasciarlo proseguire. throw $e; rilancia la stessa eccezione. Più spesso vuoi aggiungere contesto mantenendo la causa originale, ed è a questo che serve il terzo argomento del costruttore, $previous:
Il chiamante gestisce una sola ConfigException e non ha bisogno di sapere che la configurazione è in JSON, mentre getPrevious() conserva il dettaglio di basso livello per il log.
Eccezioni non catturate e un gestore globale
Un'eccezione che nessun catch gestisce termina lo script con un errore fatale che include la classe, il messaggio, il file e la riga, e uno stack trace:
<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
if ($cents <= 0) {
throw new DomainException("Amount must be positive, got $cents");
}
}
charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";
PHP stampa first charge ok e poi:
PHP Fatal error: Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
thrown in /home/index.php on line 6
Su un sito in produzione quel messaggio non deve mai arrivare ai visitatori. set_exception_handler() registra una funzione che riceve qualsiasi eccezione non catturata, un unico posto dove registrarla e stampare una pagina cortese:
Il gestore viene eseguito e poi lo script si ferma; non c'è modo di riprendere dopo il throw. Se il testo degli errori di PHP venga mostrato sulla pagina dipende da display_errors, trattato in segnalazione degli errori.
I warning non sono eccezioni
try/catch vede solo ciò che viene lanciato. Molte funzioni PHP più vecchie non lanciano eccezioni: restituiscono false o null e, al massimo, stampano un warning. json_decode() è un caso tipico, e racchiuderla in try non cambia nulla finché non le chiedi di lanciare:
Per le funzioni senza un flag del genere, verifica il valore restituito (if ($handle === false)), oppure verifica prima la precondizione (file_exists(), isset()). Un set_error_handler() che lancia ErrorException trasforma ogni warning in una vera eccezione, se vuoi una regola unica per tutto.
Ricetta: validare l'input di un form con le eccezioni
Uno schema pratico: le funzioni di validazione lanciano eccezioni, la pagina le cattura e mostra il messaggio accanto al form. Eseguilo, poi scrivi un'età nel form e premi Send: lo stesso script viene eseguito di nuovo con $_POST compilato.
Prova un campo vuoto, twelve, 9 e 30. Ogni regola che fallisce lancia un'eccezione con il proprio messaggio, e un solo catch le trasforma tutte nella stessa riga rossa. La pagina sui form parte da qui con più campi ed errori accanto a ciascuno.
Domande frequenti
Come funziona try catch in PHP?
PHP esegue il codice dentro try { }. Se qualcosa lancia un'eccezione, PHP salta il resto del blocco try ed esegue il primo blocco catch il cui tipo corrisponde, con l'oggetto lanciato nella variabile: catch (Exception $e) { echo $e->getMessage(); }. Se non viene lanciato nulla, i blocchi catch vengono saltati.
Come catturo tutte le eccezioni e gli errori in PHP?
Cattura Throwable: catch (Throwable $e). catch (Exception $e) cattura solo le eccezioni, non gli errori del motore come TypeError, ValueError o DivisionByZeroError, che estendono Error. Sia Exception sia Error implementano Throwable.
Come catturo più eccezioni in un solo blocco catch in PHP?
Separa i tipi con una barra verticale: catch (InvalidArgumentException | RangeException $e). Puoi anche scrivere più blocchi catch uno dopo l'altro; PHP usa il primo che corrisponde, quindi metti prima i tipi più specifici.
finally viene eseguito se c'è un return nel try?
Sì. finally viene eseguito dopo try e catch sia che il codice abbia restituito un valore, lanciato un'eccezione o terminato normalmente, anche quando try contiene return. Se finally stesso restituisce un valore, quel valore sostituisce quello di try, quindi evita return dentro finally.
try catch cattura i warning in PHP?
No. Warning e notice (come leggere una chiave di array mancante) non sono eccezioni, quindi catch non li vede mai. Verifica la condizione prima della chiamata, usa la modalità a eccezioni della funzione quando esiste (json_decode(..., flags: JSON_THROW_ON_ERROR)), oppure converti i warning con set_error_handler() ed ErrorException.