PHP Exception
Scopri le eccezioni PHP: come lanciare e catturare errori con try/catch/finally, i metodi della classe Exception e le eccezioni personalizzate.
Cos'è un'eccezione?
Un'eccezione è un oggetto che rappresenta un errore o una condizione imprevista che interrompe il normale flusso di un programma. Invece di lasciare che un problema faccia terminare silenziosamente lo script, si lancia un'eccezione nel punto in cui qualcosa va storto, poi la si cattura nel punto che sa come gestirla, registrarla o segnalarla.
Questa pagina spiega come lanciare e catturare eccezioni con try/catch/finally, i metodi che ogni oggetto eccezione mette a disposizione, come catturare più tipi di eccezione e come scrivere le proprie classi di eccezione.
Le eccezioni sono utili ogni volta che una funzione non può proseguire in modo significativo: input utente non valido, connessione al database fallita, file mancante o valore fuori dall'intervallo consentito. Mantengono la logica di gestione degli errori separata dalla logica normale, così il "percorso felice" rimane leggibile.
throw new Exception('Something went wrong');In PHP 7 e versioni successive, sia Exception che Error implementano l'interfaccia Throwable, quindi tutto ciò che può essere lanciato è un Throwable.
Lanciare e catturare con try / catch / finally
Il codice rischioso va racchiuso in un blocco try. Se un'istruzione al suo interno lancia un'eccezione, PHP smette di eseguire il resto del blocco try e salta al primo catch corrispondente. Il blocco finally opzionale viene sempre eseguito successivamente — sia che un'eccezione sia stata lanciata o meno — il che lo rende il posto giusto per rilasciare risorse (chiudere un file, un handle di database, un lock).
<?php
function divide($a, $b) {
if ($b === 0) {
throw new InvalidArgumentException('Division by zero is not allowed.');
}
return $a / $b;
}
try {
echo divide(10, 2), "\n"; // 5
echo divide(10, 0), "\n"; // throws — the next line never runs
} catch (InvalidArgumentException $e) {
echo 'Caught: ' . $e->getMessage() . "\n";
} finally {
echo "Done.\n";
}Output:
5
Caught: Division by zero is not allowed.
Done.Si noti che il secondo echo divide(...) non viene mai stampato, perché il lancio interrompe immediatamente il blocco try. Il blocco finally viene comunque eseguito.
Leggere le informazioni da un'eccezione
Ogni oggetto eccezione contiene dettagli utili. I metodi più comuni, tutti ereditati dalla classe base Exception, sono:
| Metodo | Restituisce |
|---|---|
getMessage() | Il messaggio di errore leggibile dall'uomo |
getCode() | Il codice di errore intero passato al costruttore |
getFile() | Il file in cui è stata creata l'eccezione |
getLine() | Il numero di riga in cui è stata creata |
getTraceAsString() | Lo stack delle chiamate come stringa, utile per il logging |
getPrevious() | L'eccezione precedente, quando una ne avvolge un'altra |
<?php
class InsufficientFundsException extends Exception {}
class Account {
private float $balance;
public function __construct(float $balance) { $this->balance = $balance; }
public function withdraw(float $amount): void {
if ($amount > $this->balance) {
throw new InsufficientFundsException(
"Cannot withdraw $amount; balance is {$this->balance}.",
100 // a custom error code
);
}
$this->balance -= $amount;
}
}
$account = new Account(50);
try {
$account->withdraw(80);
} catch (InsufficientFundsException $e) {
echo $e->getMessage() . "\n"; // Cannot withdraw 80; balance is 50.
echo 'Code: ' . $e->getCode() . "\n"; // Code: 100
}Output:
Cannot withdraw 80; balance is 50.
Code: 100Catturare più tipi di eccezione
Un singolo try può avere più blocchi catch, controllati dall'alto verso il basso — il primo il cui tipo corrisponde all'eccezione lanciata vince. Se due tipi di eccezione non correlati devono essere gestiti allo stesso modo, combinali in un unico blocco con l'operatore | (pipe) invece di duplicare il codice.
<?php
function parseAge(string $input): int {
if (!is_numeric($input)) {
throw new TypeError("'$input' is not a number.");
}
$age = (int) $input;
if ($age < 0) {
throw new RangeException("Age cannot be negative.");
}
return $age;
}
foreach (['42', 'abc', '-5'] as $value) {
try {
echo parseAge($value) . "\n";
} catch (TypeError | RangeException $e) {
echo get_class($e) . ': ' . $e->getMessage() . "\n";
}
}Output:
42
TypeError: 'abc' is not a number.
RangeException: Age cannot be negative.L'ordine è importante: poiché le eccezioni PHP formano una gerarchia, metti prima i tipi più specifici e quelli più generici (come Exception o Throwable) per ultimi, altrimenti il blocco generale catturerà tutto prima che quello specifico abbia la possibilità di farlo.
Creare eccezioni personalizzate
Oltre alle classi predefinite, puoi definire i tuoi tipi di eccezione estendendo Exception (o una classe predefinita più specifica come RuntimeException). Una classe dedicata rende i blocchi catch espressivi — puoi reagire specificamente al tuo errore — e ti consente di allegare dati aggiuntivi.
Nell'esempio Account sopra, InsufficientFundsException è un'eccezione personalizzata. Spesso una sottoclasse vuota è sufficiente; aggiungi metodi solo quando hai bisogno di comportamenti aggiuntivi:
<?php
class ValidationException extends Exception {
private array $errors;
public function __construct(string $message, array $errors = []) {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}Se sovrascrivi il costruttore, chiama sempre parent::__construct() affinché il messaggio, il codice e l'eccezione precedente vengano impostati correttamente.
Catturare
Throwable. Per gestire sia le eccezioni ordinarie che gli errori a livello di motore (come unTypeErrorda un tipo di argomento errato), catturaThrowable. È il "catch-all" più sicuro, ma usalo come ultima risorsa per non ingoiare accidentalmente bug che dovresti correggere:try { // risky code } catch (Throwable $e) { error_log($e->getMessage()); }
Buone pratiche
- Cattura solo ciò che puoi gestire. Lascia propagare le eccezioni da cui non puoi recuperare fino a un gestore centrale.
- Lancia presto, cattura tardi. Lancia nel punto esatto in cui un valore diventa non valido; cattura dove puoi effettivamente rispondere.
- Usa tipi specifici. Le sottoclassi personalizzate o predefinite sono migliori di un semplice
Exceptionper il controllo del flusso. - Scrivi messaggi informativi e usa
getCode()per la categorizzazione leggibile dalle macchine. - Registra, non silenziare. Registra le eccezioni con
error_log()invece di ingoiarle. Per le eccezioni non catturate, registra un gestore globale conset_exception_handler(). - Non usare le eccezioni per il flusso normale. Riservale per condizioni genuinamente eccezionali.
Come scorre il controllo
Il diagramma seguente mostra il percorso che l'esecuzione segue attraverso una struttura try/catch/finally:
graph TD
Try[Try Block] -->|No Exception| Finally[Finally Block]
Try -->|Exception Thrown| Catch[Catch Block]
Catch --> Finally