Skip to content

7. Eccezioni ed errori

Quando un metodo di una classe incontra un errore irreversibile (file inesistente, database non connesso, connessione di rete non disponibile), non visualizza un errore su una console (file, database) ma genera un’eccezione. Tutte le eccezioni estendono la classe [\Exception]. Oltre alle eccezioni, il funzionamento interno di PHP genera anche errori la cui classe base è la classe [\Error]. Entrambe le classi implementano l’interfaccia PHP [\Throwable].

7.1. L'albero degli script

Image

7.2. L’interfaccia [\Throwable]

L’interfaccia [\Throwable] è la seguente:

Image

La funzione dei metodi dell’interfaccia è la seguente:

Image

7.3. Le eccezioni predefinite in PHP 7

PHP 7 definisce diverse classi di eccezioni:

Image

  • in [1], le eccezioni predefinite in PHP;
  • In [2], le eccezioni della libreria SPL (Libreria standard PHP) di PHP 7. La libreria SPL è una raccolta di classi e interfacce destinate a risolvere i problemi che gli sviluppatori incontrano frequentemente.

7.4. Gli errori predefiniti in PHP 7

PHP 7 definisce diverse classi di errori:

Image

La classe [\Error] è la classe padre di tutti gli errori predefiniti in PHP. La classe [ErrorException] consente di incapsulare un'istanza della classe [\Error] in un'istanza della classe [\Exception]. Ciò permette di uniformare la gestione degli errori trattando esclusivamente le eccezioni.

7.5. Esempio 1

Il primo esempio [exceptions-01.php] mostra sia errori PHP sia un'eccezione:


<?php

// visualizzazione di tutti gli errori
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// codice --------
$var=[];
// chiave sconosciuta
print $var["abcd"];
// divisione per zero
$var=7/0;
var_dump($var);
// matrice a valori fissi
$array = new \SplFixedArray(5);
$array[1] = 2;
$array[4] = "foo";
// indice fuori dai limiti
$array[5]=8;

Commenti

  • riga 4: si richiede a PHP di segnalare tutti gli errori. Il secondo parametro è il livello di errore richiesto:

Image

Image

  • riga 5: si richiede di visualizzare gli errori sulla console;
  • riga 9: si accede a un elemento inesistente dell'array [$var];
  • riga 11: si esegue una divisione per zero;
  • riga 14: si crea un'istanza della classe [SplFixedArray]. Questa classe consente di creare un array a intervallo fisso e con indici interi;
  • riga 18: si accede a un elemento inesistente dell'array;

Risultati

1
2
3
4
5
6
7
8
9
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 9

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 11
float(INF)

Fatal error: Uncaught RuntimeException: Index invalid or out of range in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php:18
Stack trace:
#0 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-01.php on line 18

Commenti

  • riga 1 dei risultati: l'accesso a una chiave inesistente di un array provoca 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 provoca 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] provoca un'eccezione di tipo [RuntimeException] e interrompe l'esecuzione dello script;

7.6. Gestione delle eccezioni

Lo script [exceptions-02.php] mostra come gestire le eccezioni:


<?php

// vengono visualizzati tutti gli errori
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");
// il codice è racchiuso in un try / catch
try {
  $var = [];
  // chiave sconosciuta
  print $var["abcd"];
  // divisione per zero
  $var = 7 / 0;
  var_dump($var);
  // array con indici fissi
  $array = new \SplFixedArray(5);
  $array[1] = 2;
  $array[4] = "foo";
  // indice fuori dai limiti
  $array[5] = 8;
  // verifica
  print "ce message ne sera pas affiché\n";
} catch (\Throwable $ex) {
  // \Throwable è l'interfaccia implementata dalla maggior parte degli errori e delle eccezioni
  // viene visualizzata l'eccezione
  print "erreur, message : " . $ex->getMessage() . ", type : " . get_class($ex) . "\n";
}

Commenti

  • lo script è quello presentato nel paragrafo precedente. Solo che ora il codice delle righe 8-19, che potrebbe causare errori, è stato racchiuso in una struttura try/catch: se il codice delle righe 8-21 genera (lancia) un'eccezione o un errore, questa verrà gestita dalla clausola catch delle righe 22-26;
  • riga 22: il parametro della clausola [catch] è il tipo di eccezione o di errore che si desidera gestire. Impostando come tipo [\Throwable], che è un'interfaccia, si indica che si desidera gestire qualsiasi istanza di classe che implementi l'interfaccia [\Throwable]. Poiché tutte le classi di errori ed eccezioni implementano questa interfaccia, la clausola [catch] gestisce in questo caso qualsiasi errore/eccezione incapsulato in una classe;
  • riga 19: l’istruzione che genera l’errore e dà origine all’eccezione. Non appena si verifica un’eccezione, si passa alla clausola [catch]. Il codice successivo alla riga 19 non verrà quindi eseguito;

Risultati

1
2
3
4
5
Notice: Undefined index: abcd in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 10

Warning: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-02.php on line 12
float(INF)
erreur, message : Index invalid or out of range, type : RuntimeException

Commenti sui risultati

  • righe 1 e 3: si riscontrano gli errori di livello [E_NOTICE] e [E_WARNING]. Questi errori non sono eccezioni e non sono quindi gestiti dalla clausola [catch];
  • riga 5: il messaggio di errore è riportato 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

// vengono visualizzati tutti gli errori
ini_set("error_reporting", E_ALL);
ini_set("display_errors", "on");

// un array a intervallo fisso
$array = new \SplFixedArray(5);
try {
  // indice fuori dai limiti
  $array[5] = 8;
} catch (\Throwable $ex) {
  // visualizzazione del messaggio di errore
  print "Erreur 1 : " . $ex->getMessage() . "\n";
}

try {
  // indice fuori dai limiti
  $array[5] = 8;
} catch (\Exception $ex) {
  // visualizzazione del messaggio di errore
  print "Erreur 2 : " . $ex->getMessage() . "\n";
}

try {
  // valore fuori intervallo
  $array[5] = 8;
} catch (\RuntimeException $ex) {
  // visualizzazione messaggio di errore
  print "Erreur 3 : " . $ex->getMessage() . "\n";
}
try {
  // divisione per 0
  intdiv(5, 0);
} catch (\Throwable $ex) {
  // visualizzazione messaggio di errore
  print "Erreur 4 : " . $ex->getMessage() . "\n";
}

try {
  // divisione per 0
  intdiv(5, 0);
} catch (\DivisionByzeroError $ex) {
  // visualizzazione messaggio di errore
  print "Erreur 5 : " . $ex->getMessage() . "\n";
}

try {
  // divisione per 0
  intdiv(5, 0);
} catch (\Error $ex) {
  // visualizzazione del messaggio di errore
  print "Erreur 6 : " . $ex->getMessage() . "\n";
}

try {
  // divisione per 0
  intdiv(5, 0);
} catch (\Exception $ex) {
  // visualizzazione del messaggio di errore
  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 generava un’eccezione di tipo [RuntimeException];
    • riga 12: gestisce un errore di tipo [\Throwable]. È valido poiché il tipo [RuntimeException] deriva dal tipo [\Exception] che implementa l’interfaccia [\Throwable];
    • riga 20: gestisce un errore di tipo [\Exception]. È valido poiché il tipo [RuntimeException] deriva dal tipo [\Exception];
    • riga 28: gestisce un errore di tipo [\RuntimeException]. È il metodo da privilegiare poiché corrisponde esattamente al tipo 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 è nullo, viene generata l’eccezione [\DivisionByzeroError];
    • riga 35: si intercetta qualsiasi errore che implementi l’interfaccia [\Throwable]. È corretto;
    • riga 43: si intercetta il tipo esatto dell’errore: questo è il metodo da privilegiare;
    • riga 51: si intercetta il tipo [\Error]. È valido poiché la classe [DivisionByzeroError] estende la classe [Error];
    • riga 59: viene intercettato il tipo [\Exception]. Non è valido poiché la classe [DivisionByzeroError] non ha alcun legame con la classe [\Exception];

Risultati

Erreur 1 : Index invalid or out of range
Erreur 2 : Index invalid or out of range
Erreur 3 : Index invalid or out of range
Erreur 4 : Division by zero
Erreur 5 : Division by zero
Erreur 6 : Division by zero

Fatal error: Uncaught DivisionByZeroError: Division by zero in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php:58
Stack trace:
#0 C:\Data\st-2019\dev\php7\php5-esempi\esempi\eccezioni\exceptions-03.php(58): intdiv(5, 0)
#1 {main}
thrown in C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-03.php on line 58

7.8. Clausola [finally]

La struttura try / catch può avere un terzo elemento e diventare una struttura try / catch / finally. Il codice della clausola [finally] viene eseguito nei due casi seguenti:

  • la clausola [try] non genera un'eccezione. Viene quindi eseguita per intero, dopodiché l'esecuzione del codice passa 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 del codice passa quindi alla clausola [catch], che viene eseguita per intero. Successivamente, l'esecuzione del codice passa alla clausola [finally], che viene eseguita per intero;

infine, viene comunque eseguito il codice della clausola [finally]. Questo scenario è utile nel seguente caso:

  • nella clausola [try], il codice ha acquisito delle risorse (file, database, connessioni di rete, code). In genere queste risorse richiedono un elevato consumo di memoria. È quindi necessario restituirle (si dice più comunemente «chiuderle») non appena possibile;
  • se l’acquisizione delle risorse è avvenuta nello scenario [try], la loro restituzione verrà effettuata nello scenario [finally]. Ciò garantisce che in ogni caso (con o senza errore) le risorse acquisite vengano restituite al sistema;

Il seguente script [exemples/exceptions/exceptions-04.php] illustra il funzionamento della clausola [finally] in diverse situazioni:


<?php

// oppure crea un'istanza di eccezione
$e = new \Exception("Erreur…");    
var_dump($e);

// primo test
try {
  print "Premier test\n";
  throw $e;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

// secondo test
try {
  print "Second test\n";
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

// terzo test
try {
  print "Troisième test\n";
  return;
} catch (\Exception $ex1) {
  print $ex1->getMessage() . "\n";
} finally {
  print "Terminé\n";
}

Commenti al codice

  • riga 4: $e è un'istanza della classe predefinita [\Exception]. Verrà lanciata in diversi punti;
  • righe 8-15: l’eccezione $e viene generata all’interno di [try] (riga 10);
  • riga 11: l’eccezione [\Exception] viene intercettata e il relativo messaggio di errore viene scritto sulla console;
  • righe 13-15: la clausola [finally] scrive un messaggio. In base a quanto detto in precedenza, questo messaggio dovrebbe essere scritto sempre, indipendentemente dal fatto che ci sia o meno un errore nel [try];
  • righe 18-24: non vi è alcun errore nel [try]. Anche in questo caso si dovrebbe passare al [finally];
  • righe 27-34: nel blocco «try» è presente un’istruzione [return] e non si verifica alcun errore. Ci si può quindi chiedere se si passerà alla clausola [finally]. L’esecuzione dimostra che sì;

Risultati

1
2
3
4
5
6
7
Premier test
Erreur…
Terminé
Second test
Terminé
Troisième test
Terminé

Esaminiamo un altro caso, [exceptions-05.php]:


<?php

// quarto 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 la clausola [catch]. È possibile;

Risultati

Quatrième test

7.9. Creare le proprie classi di eccezioni

In un progetto di una certa entità, è utile distinguere i diversi errori incapsulandoli in diverse classi di eccezioni. Nello script precedente, abbiamo visto che qualsiasi eccezione poteva essere intercettata da una clausola [catch (\Throwable]. Questo è consigliabile se non si ha alcuna idea dell’errore intercettato e se 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 gli errori tra loro.

Esaminiamo il seguente script [exceptions-06.php]:


<?php

// definiamo la nostra famiglia di eccezioni
class Exception1 extends \RuntimeException {
  
}

class Exception2 extends \RuntimeException {
  
}

// oppure utilizziamo le nostre eccezioni
$e1 = new Exception1("Erreur1…");
var_dump($e1);
$e2 = new Exception2("Erreur2…");
var_dump($e2);

// primo test
print ("premier test\n");
try {
  // si genera un'eccezione di tipo Exception1
  throw $e1;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}

// secondo test
print ("second test\n");
try {
  // si genera un'eccezione di tipo Exception2
  throw $e2;
} catch (Exception1 $ex1) {
  print "Exception 1" . "\n";
  print $ex1->getMessage() . "\n";
} catch (Exception2 $ex2) {
  print "Exception 2" . "\n";
  print $ex2->getMessage() . "\n";
}

// terzo test
print ("troisième test\n");
try {
  // viene generata un'eccezione di tipo Exception1
  throw $e1;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}

// quarto test
print ("quatrième test\n");
try {
  // viene generata un'eccezione di tipo Exception2
  throw $e2;
} catch (Exception1 | Exception2 $ex) {
  print "Exception 1 ou 2" . "\n";
  print $ex->getMessage() . "\n";
}

Commenti

  • righe 4-10: si definiscono due classi, [Exception1] e [Exception2], entrambe derivate dalla classe predefinita [\RuntimeException]. Il corpo di queste classi è vuoto. In altre parole, vengono utilizzate solo per i loro tipi: è proprio perché hanno tipi diversi che sarà possibile distinguere queste due eccezioni nelle clausole [catch];
  • righe 13-16: si definiscono due variabili $e1 e $e2 aventi rispettivamente i tipi [Exception1] e [Exception2];
  • righe 20-29: è presente una struttura try / catch / catch. Ciò consente 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

object(Exception1)#1 (7) {
  ["message":protected]=>
  string(10) "Erreur1…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(13)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
object(Exception2)#2 (7) {
  ["message":protected]=>
  string(10) "Erreur2…"
  ["string":"Exception":private]=>
  string(0) ""
  ["code":protected]=>
  int(0)
  ["file":protected]=>
  string(76) "C:\Data\st-2019\dev\php7\php5-exemples\exemples\exceptions\exceptions-06.php"
  ["line":protected]=>
  int(15)
  ["trace":"Exception":private]=>
  array(0) {
  }
  ["previous":"Exception":private]=>
  NULL
}
premier test
Exception 1
Erreur1…
second test
Exception 2
Erreur2…
troisième test
Exception 1 ou 2
Erreur1…
quatrième test
Exception 1 ou 2
Erreur2…

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 e definire così una pila di eccezioni. L’attributo [previous] consentirà di sfruttare questa pila;

7.10. Rilanciare un'eccezione

Un'eccezione può essere generata più volte, come illustrato dal seguente script [exceptions-07.php]:


<?php

try {
  try {
    // si genera un'eccezione
    throw new \Exception("test");
  } catch (\Exception $ex) {
    // si rilancia l'eccezione intercettata
    throw $ex;
  } finally {
    // si passerà correttamente al blocco finally
    print "finally 1\n";
  }
} catch (\Exception $ex2) {
  // si recupera correttamente l'eccezione iniziale
  print $ex2->getMessage() . " dans try / catch / finally externe\n";
} finally {
  // si entrerà correttamente nel blocco finally
  print "finally 2\n";
}

Commenti

  • riga 6: si genera un'eccezione;
  • riga 7: l'eccezione viene intercettata;
  • riga 9: viene rilanciata. Passa quindi nella struttura try / catch / finally del livello superiore;
  • riga 14: viene nuovamente intercettata;
  • righe 10-12: l'esecuzione mostra che, anche dopo il [throw] della riga 9, si passa effettivamente alla clausola [finally] del try / catch / finally;

Risultati

1
2
3
finally 1
test dans try / catch / finally externe
finally 2

7.11. Gestione di una pila di eccezioni

Un'eccezione può incapsulare un'altra eccezione, che a sua volta può incapsularne un'altra, formando infine una pila di eccezioni. Ecco un esempio [exceptions-08.php]:

<?php

// si definisce la propria famiglia di eccezioni
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// oppure utilizziamo le nostre eccezioni
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// gestione dell'eccezione corrente
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
   // eccezione precedente
  $e = $e->getPrevious();
   // messaggio di errore
  print $e->getMessage() . "\n";
}

Commenti

  • righe 4-14: definiscono tre classi di eccezioni derivate dall’eccezione predefinita [RuntimeException];
  • riga 17: un'istanza della classe [Exception3] è incapsulata in un'istanza della classe [Exception2], che a sua volta è incapsulata in un'istanza della classe [Exception1]. Il costruttore utilizzato in questo caso è quello della classe [Exception]:

Image

Il terzo parametro del costruttore consente di incapsulare un'eccezione. Ciò può essere utile nel seguente scenario:

  • si definisce 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, nel metodo M possono verificarsi altri tipi di eccezioni. Per segnalare un errore al codice che chiama il metodo M, si incapsuleranno quindi queste eccezioni nel tipo [Exception1] che verrà lanciato. Ciò consente di non perdere le informazioni contenute nell’eccezione incapsulata e che sono state la causa originaria dell’errore;
  • le righe 20-27 mostrano come gestire lo stack delle eccezioni interne a 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…