7. Eccezioni ed errori
Quando un metodo di classe incontra un errore irreversibile (file inesistente, database non connesso, connessione di rete interrotta), non visualizza un errore su una console (file, database) ma genera un'eccezione. Tutte le eccezioni estendono la classe [\Exception]. Oltre alle eccezioni, anche le operazioni interne di PHP generano errori la cui classe base è la classe [\Error]. Entrambe le classi implementano l'interfaccia PHP [\Throwable].
7.1. La struttura delle directory degli script

7.2. L'interfaccia [\Throwable]
L'interfaccia [\Throwable] è la seguente:

Il ruolo dei metodi dell'interfaccia è il seguente:

7.3. Eccezioni predefinite in PHP 7
PHP 7 definisce diverse classi di eccezioni:

- in [1], le eccezioni predefinite in PHP;
- in [2], le eccezioni della SPL (Standard PHP Library) in PHP 7. La SPL è una raccolta di classi e interfacce progettate per risolvere i problemi che gli sviluppatori incontrano frequentemente.
7.4. Errori predefiniti in PHP 7
PHP 7 definisce diverse classi di errore:

La classe [\Error] è la classe padre di tutti gli errori predefiniti in PHP. La classe [ErrorException] consente di incapsulare un'istanza della classe [\Error] all'interno di un'istanza della classe [\Exception]. Ciò consente una gestione standardizzata degli errori occupandosi esclusivamente delle eccezioni.
7.5. Esempio 1
Il primo esempio [exceptions-01.php] mostra sia gli errori PHP che un'eccezione:
<?php
// display all errors
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// code --------
$var=[];
// unknown key
print $var["abcd"];
// division by zero
$var=7/0;
var_dump($var);
// fixed terminal board
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index outside the limits
$array[5]=8;
Commenti
- Riga 4: Indichiamo a PHP di segnalare tutti gli errori. Il secondo parametro è il livello di errore richiesto:


- riga 5: chiediamo di visualizzare gli errori sulla console;
- riga 9: accediamo a un elemento inesistente dell'array [$var];
- riga 11: eseguiamo una divisione per zero;
- riga 14: creiamo un'istanza della classe [SplFixedArray]. Questa classe ci permette di creare un array con limiti fissi e indici interi;
- riga 18: accediamo a un elemento inesistente dell'array;
Risultati
Commenti
- Riga 1 dei risultati: L'accesso a una chiave di array inesistente causa un errore PHP di livello [E_NOTICE]. Ciò non interrompe l'esecuzione dello script;
- Riga 3 dei risultati: la divisione di un numero per zero causa un errore PHP di livello [E_WARNING]. Ciò non interrompe l'esecuzione dello script;
- Righe 6–9 dei risultati: l'accesso a un indice inesistente di un array [SplFixedArray] causa un [RuntimeException] e interrompe l'esecuzione dello script;
7.6. Gestione delle eccezioni
Lo script [exceptions-02.php] mostra come gestire le eccezioni:
<?php
// all errors are displayed
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// surround the code with a try / catch
try {
$var = [];
// unknown key
print $var["abcd"];
// division by zero
$var = 7 / 0;
var_dump($var);
// fixed terminal board
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// index outside the limits
$array[5] = 8;
// check
print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
// \Throwable is the interface implemented by most errors and exceptions
// exception is displayed
print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}
Commenti
- Questo script è quello presentato nel paragrafo precedente. Tuttavia, ora abbiamo racchiuso il codice delle righe 8–19 — che potrebbe causare errori — in un blocco try/catch: se il codice delle righe 8–21 causa (genera) un'eccezione o un errore, questo verrà gestito dalla clausola catch nelle righe 22–26;
- Riga 22: Il parametro della clausola [catch] è il tipo di eccezione o errore da gestire. Specificando il tipo come [\Throwable] — che è un'interfaccia — indichiamo che vogliamo gestire qualsiasi istanza di classe che implementi l'interfaccia [\Throwable]. Poiché tutte le classi di errore ed eccezione implementano questa interfaccia, la clausola [catch] qui gestisce qualsiasi errore o eccezione incapsulato in una classe;
- riga 19: l'istruzione che genera l'errore e solleva l'eccezione. Non appena si verifica un'eccezione, il controllo passa alla clausola [catch]. Il codice che segue la riga 19 non verrà quindi eseguito;
Risultati
Commenti sui risultati
- righe 1 e 3: vediamo errori di livello [E_NOTICE] e [E_WARNING]. Questi errori non sono eccezioni e quindi non vengono gestiti dalla clausola [catch];
- riga 5: abbiamo il messaggio di errore scritto nella clausola [catch]. Si è quindi verificata un'eccezione derivata da [\Exception] o un errore derivato da [\Error]. Qui vediamo che si tratta della classe [\RuntimeException];
7.7. Parametri della clausola [catch]
Esaminiamo il seguente script [exceptions-03.php]:
<?php
// all errors are displayed
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// a fixed terminal board
$array = new \SplFixedArray(5);
try {
// index outside the limits
$array[5] = 8;
} catch (\Throwable $ex) {
// error message display
print "Erreur 1 : " . $ex->getMessage() . "\n";
}
try {
// index outside the limits
$array[5] = 8;
} catch (\Exception $ex) {
// error message display
print "Erreur 2 : " . $ex->getMessage() . "\n";
}
try {
// index outside the limits
$array[5] = 8;
} catch (\RuntimeException $ex) {
// error message display
print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Throwable $ex) {
// error message display
print "Erreur 4 : " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
// error message display
print "Erreur 5 : " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Error $ex) {
// error message display
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
try {
// division by 0
intdiv(5, 0);
} catch (\Exception $ex) {
// error message display
print "Erreur 6 : " . $ex->getMessage() . "\n";
}
Commenti
- righe 8–31: 3 modi diversi per gestire l'eccezione generata dall'uso di un indice errato con la classe [\SplFixedArray]. Abbiamo visto che questo errore genera una [RuntimeException];
- riga 12: gestisce un errore di tipo [\Throwable]. Ciò è valido poiché il tipo [RuntimeException] deriva dal tipo [\Exception], che implementa l'interfaccia [\Throwable];
- riga 20: gestisce un errore di tipo [\Exception]. Questo è valido poiché il tipo [RuntimeException] deriva dal tipo [\Exception];
- riga 28: gestisce un errore di tipo [\RuntimeException]. Questo è il metodo preferito poiché è il tipo esatto dell'eccezione generata;
- righe 32–62: 4 modi diversi per gestire l'eccezione generata dalla funzione [intdiv] quando le viene passato un divisore pari a 0. La funzione [intdiv(int $dividend, int $divisor): int] esegue la divisione intera $dividend / $divisor. Quando il divisore è zero, viene generata l'eccezione [\DivisionByzeroError];
- riga 35: intercettiamo qualsiasi errore che implementi l'interfaccia [\Throwable]. Questo è valido;
- riga 43: intercettiamo il tipo esatto dell'errore: questo è il metodo preferito;
- riga 51: viene intercettato il tipo [\Error]. Questo è valido poiché la classe [DivisionByzeroError] estende la classe [Error];
- riga 59: viene intercettato il tipo [\Exception]. Ciò non è corretto perché la classe [DivisionByzeroError] non ha alcun collegamento con la classe [\Exception];
Risultati
7.8. clausola [finally]
La struttura try/catch può avere un terzo elemento e diventare una struttura try/catch/finally. Il codice nella clausola [finally] viene eseguito nei seguenti due casi:
- la clausola [try] non genera un'eccezione. Viene quindi eseguita per intero e l'esecuzione del codice procede alla clausola [finally], che viene eseguita per intero;
- la clausola [try] genera un'eccezione. Viene quindi eseguita fino all'istruzione che genera l'eccezione. L'esecuzione procede quindi alla clausola [catch], che viene eseguita per intero. L'esecuzione procede quindi alla clausola [finally], che viene eseguita per intero;
Infine, il codice nel blocco [finally] viene sempre eseguito. Questo scenario è utile nel seguente caso:
- nel blocco [try], il codice ha acquisito risorse (file, database, connessioni di rete, code). Queste risorse sono generalmente ad alto consumo di memoria. Devono quindi essere rilasciate (il più delle volte indicate come "chiuse") il prima possibile;
- Se le risorse sono state acquisite nel blocco [try], il loro rilascio dovrebbe essere inserito nel blocco [finally]. Ciò garantisce che, in tutti i casi (indipendentemente dal verificarsi o meno di un errore), le risorse acquisite vengano restituite al sistema;
Il seguente script [examples/exceptions/exceptions-04.php] mostra come funziona la clausola [finally] in varie situazioni:
<?php
// or create an exception instance
$e = new \Exception("Erreur…");
var_dump($e);
// first test
try {
print "Premier test\n";
throw $e;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// second test
try {
print "Second test\n";
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
// third test
try {
print "Troisième test\n";
return;
} catch (\Exception $ex1) {
print $ex1->getMessage() . "\n";
} finally {
print "Terminé\n";
}
Commenti nel codice
- riga 4: $e è un'istanza della classe predefinita [\Exception]. La lanceremo in vari punti;
- righe 8–15: l'eccezione $e viene generata nel blocco [try] (riga 10);
- riga 11: l'eccezione [\Exception] viene intercettata e il suo messaggio di errore viene scritto sulla console;
- righe 13–15: la clausola [finally] stampa un messaggio. In base a quanto detto in precedenza, questo messaggio dovrebbe essere sempre stampato, indipendentemente dal fatto che ci sia un errore nel blocco [try];
- righe 18–24: non c'è alcun errore nel [try]. Dovremmo anche entrare nel blocco [finally] qui;
- righe 27–34: nel blocco `try` è presente un'istruzione `return` e non si verifica alcun errore. Ci si potrebbe quindi chiedere se si entrerà nella clausola `finally`. L'esecuzione dimostra che sì, ci si entra;
Risultati
Esaminiamo un altro caso [exceptions-05.php]:
<?php
// fourth test
try {
print "Quatrième test\n";
exit;
} finally {
print "Terminé\n";
}
Commenti
- riga 6: l'istruzione [exit] interrompe immediatamente l'esecuzione dello script: la clausola [finally] non viene eseguita;
- righe 4–9: un esempio di try / catch / finally senza una clausola [catch]. Ciò è possibile;
Risultati
7.9. Creazione di classi di eccezione personalizzate
In un progetto di una certa entità, è utile distinguere tra diversi errori incapsulandoli in diverse classi di eccezione. Nello script precedente, abbiamo visto che qualsiasi eccezione poteva essere intercettata da una clausola [catch (\Throwable]. Questo è consigliabile se non si ha idea di quale sia l’errore intercettato e la gestione è la stessa per tutti gli errori. A volte è così, ma spesso è necessario adattare la gestione al tipo esatto di errore. È quindi necessario distinguere tra gli errori.
Esaminiamo il seguente script [exceptions-06.php]:
<?php
// we define our own family of exceptions
class Exception1 extends \RuntimeException {
}
class Exception2 extends \RuntimeException {
}
// or use our exceptions
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);
// first test
print ("premier test\n");
try {
// throw an Exception1 type
throw $e1;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// second test
print ("second test\n");
try {
// throw an Exception2 type
throw $e2;
} catch (Exception1 $ex1) {
print "Exception 1" . "\n";
print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
print "Exception 2" . "\n";
print $ex2->getMessage() . "\n";
}
// third test
print ("troisième test\n");
try {
// throw an Exception1 type
throw $e1;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
// fourth test
print ("quatrième test\n");
try {
// throw an Exception2 type
throw $e2;
} catch (Exception1 | Exception2 $ex) {
print "Exception 1 ou 2" . "\n";
print $ex->getMessage() . "\n";
}
Commenti
- righe 4–10: definiamo due classi, [Exception1] e [Exception2], entrambe derivate dalla classe predefinita [\RuntimeException]. Il corpo di queste classi è vuoto. In altre parole, le utilizziamo solo per i loro tipi nell'istruzion : è proprio grazie ai loro tipi diversi che potremo distinguere tra queste due eccezioni nelle clausole [catch];
- righe 13–16: definiamo due variabili, $e1 e $e2, con i tipi [Exception1] e [Exception2], rispettivamente;
- righe 20–29: abbiamo una struttura try/catch/catch. Questo ci permette di gestire diversi tipi di eccezioni con diverse clausole [catch];
- riga 23: intercetta le eccezioni di tipo [Exception1];
- riga 26: intercetta le eccezioni di tipo [Exception2];
- riga 49: intercetta le eccezioni di tipo [Exception1] o (|) [Exception2];
Risultati
Commenti sui risultati
- righe 1–17: il “contenuto” di un’eccezione:
- righe 2–3: il messaggio di errore;
- righe 6–7: il codice di errore;
- righe 8–9: il nome del file in cui si è verificata l'eccezione;
- righe 10–11: la riga in cui si è verificata l'eccezione;
- righe 15–16: l'eccezione precedente. Un'eccezione può incapsulare un'altra eccezione, definendo così uno stack di eccezioni. L'attributo [previous] consente di accedere a questo stack;
7.10. Rilancio di un'eccezione
Un'eccezione può essere generata più volte, come mostrato nel seguente script [exceptions-07.php]:
<?php
try {
try {
// throw an exception
throw new \Exception("test");
} catch (\Exception $ex) {
// the intercepted exception is re-launched
throw $ex;
} finally {
// we'll make it to the finally
print "finally 1\n";
}
} catch (\Exception $ex2) {
// the initial exception is recovered
print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
// we'll make it to the finally
print "finally 2\n";
}
Commenti
- riga 6: generiamo un'eccezione;
- riga 7: la intercettiamo;
- riga 9: la rilanciamo. Passa quindi attraverso il blocco try/catch/finally al livello superiore;
- riga 14: viene intercettata nuovamente;
- righe 10–12: l'esecuzione mostra che anche dopo il [throw] nella riga 9, il controllo passa effettivamente alla clausola [finally] del blocco try/catch/finally;
Risultati
7.11. Lavorare con uno stack di eccezioni
Un'eccezione può incapsulare un'altra eccezione, che a sua volta può incapsularne un'altra, formando in definitiva uno stack di eccezioni. Ecco un esempio [exceptions-08.php]:
Commenti
- righe 4–14: definiscono tre classi di eccezione derivate dall'eccezione predefinita [RuntimeException];
- riga 17: un'istanza della classe [Exception3] è incapsulata all'interno di un'istanza della classe [Exception2], che a sua volta è incapsulata all'interno di un'istanza della classe [Exception1]. Il costruttore utilizzato qui è il costruttore della classe [Exception]:
![]()
Il terzo parametro del costruttore consente di incapsulare un'eccezione. Ciò può essere utile nel seguente scenario:
- definiamo un metodo M che può generare un'eccezione di tipo [Exception1] e solo di questo tipo per motivi di compatibilità, ad esempio con un'interfaccia;
- tuttavia, all'interno del metodo M possono verificarsi altri tipi di eccezioni. Per propagare un errore al codice che chiama il metodo M, incapsuleremo quindi queste eccezioni all'interno del tipo [Exception1], che lanceremo. Ciò garantisce che le informazioni contenute nell'eccezione incapsulata — che era la causa originale dell'errore — non vadano perse;
- Le righe 20–27 mostrano come gestire lo stack di eccezioni annidate all'interno di un'eccezione;
Risultati
object(Exception1)#1 (7) {
["message":protected]=>
string(11) "Erreur 1…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(1)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception2)#2 (7) {
["message":protected]=>
string(11) "Erreur 2…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(2)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
object(Exception3)#3 (7) {
["message":protected]=>
string(11) "Erreur 3…"
["string":"Exception":private]=>
string(0) ""
["code":protected]=>
int(0)
["file":protected]=>
string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-08.php"
["line":protected]=>
int(17)
["trace":"Exception":private]=>
array(0) {
}
["previous":"Exception":private]=>
NULL
}
}
}
Erreur 1…
Erreur 2…
Erreur 3…