Skip to content

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

Image

7.2. L'interfaccia [\Throwable]

L'interfaccia [\Throwable] è la seguente:

Image

Il ruolo dei metodi dell'interfaccia è il seguente:

Image

7.3. 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 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:

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] 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:

Image

Image

  • 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

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 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

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: 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

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-exemples\exemples\exceptions\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 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

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
 
// 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

Quatrième test

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

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, 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

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

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]:

<?php

// we define our own family of exceptions
class Exception1 extends \RuntimeException {

}

class Exception2 extends \RuntimeException {

}

class Exception3 extends \RuntimeException {

}

// or use our exceptions
$e1 = new Exception1("Erreur 1…", 1, new Exception2("Erreur 2…", 2, new Exception3("Erreur 3…")));
var_dump($e1);
// exploiting the current exception
print $e1->getMessage() . "\n";
$e = $e1;
while ($e->getPrevious() !== NULL) {
  // previous exception
  $e = $e->getPrevious();
  // error message
  print $e->getMessage() . "\n";
}

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]:

Image

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…