Gestione degli errori in PHP

La corretta gestione degli errori è un aspetto tanto fondamentale quanto trascurato dello sviluppo software che fin troppo spesso viene affrontato troppo tardi o senza la dovuta attenzione. In questo articolo vedremo come gestire gli errori sfruttando i meccanismi delle Exception (eccezioni) di PHP.

Exception in PHP

PHP con la versione 5 ha acquisito la nozione di Exception prendendo spunto da altri linguaggi di programmazione object-oriented come Java o C#. Il meccanismo di base è il seguente:

try {
    // (1) codice che lancia un'eccezione
    throw new Exception('Errore di tipo bla bla bla', CODICE_ERRORE);
}
catch(Exception $e) {
    // (2) gestione dell'eccezione
}

 

Quando una funzione PHP fallisce viene lanciata un’eccezione, ovvero un’istanza della classe Exception o una sua sotto-classe. Il lancio dell’eccezione può avvenire durante una chiamata ad una funzione di libreria o nel nostro stesso codice, utilizzando il comando throw come nella sezione (1) dell’esempio qui sopra.

Strutturando il codice in questo modo, è facile gestire molteplici situazioni di errore in un modo molto semplice e facilmente estendibile. È possibile infatti definire le proprie eccezioni estendendo la classe Exception ed utilizzando più di un blocco catch. Ipotizziamo ad esempio di dover gestire l’upload di file, per distinguere i vari errori potremmo definire queste eccezioni:

  • ConfigurationException nel caso il server o l’applicazione non sia stata configurata correttamente per gestire l’upload;
  • FileTooBigException nel caso l’utente abbia tentato di inviare un file troppo grande;
  • WriteException nel caso non sia possibile scrivere sul filesystem, per problemi di permessi o altro.

Supponiamo inoltre che l’upload vero e proprio sia gestito da una funzione upload_file(); il codice che chiama la funzione avrà dunque questa forma:

try {
  upload_file();
}
catch(ConfigurationException $e) {
  echo 'Errore di configurazione';
}
catch(FileTooBigException $e) {
  echo 'File troppo grande';
}
catch(WriteException $e) {
  echo 'Impossibile scrivere su filesystem';
}
catch(Exception $e) {
  echo 'Errore inaspettato: ' . $e->getMessage();
}

 

Nell’ultimo catch, come potete notare, catturiamo le eccezioni che non rientrano nella casistica sopra esposta; aggiungere un blocco catch(Exception $e) generico rappresenta quindi una “rete di protezione” efficace contro gli errori inaspettati.

Qualche consiglio utile

Saper catturare le eccezioni non basta, ma è necessario che l’applicazione stessa sia strutturata correttamente. In caso contrario, la gestione stessa degli errori potrebbe provocarne altri! Una situazione davvero pericolosa, soprattutto se la nostra applicazione è un gestionale.

Uno degli errori che si commettono più di frequente è il seguente:

try {
  // codice che fallisce
  $risultati = funzione_che_genera_eccezione();
}
catch(Exception $ex) {
  // mostra messaggio d'errore
  $errorMessage = "Si è verificato un errore: " . $ex->getMessage();
}

// SBAGLIATO: continuo come se non fosse successo nulla!
echo 'Risultato: ' . $risultati;

 

Qui lo sbaglio è che, nonostante l’eccezione sia stata catturata e l’errore sia stato notificato, l’applicazione agisce come se non ci sia stato alcun problema. A seconda dell’applicazione, questo potrebbe portare a:

  • ulteriori eccezioni lanciate durante l’esecuzione della pagina, per variabili non inizializzate o con valori non corretti;
  • l’utente potrebbe non accorgersi dell’errore e continuare a combinare danni.

Il pattern che vedremo ora è perfettamente lecito e si riferisce alla classica pagina PHP “tutto in uno”, solitamente presente in applicazioni molto semplici o siti web; questo tipo di pagina contiene sia il codice PHP per la logica di business che l’HTML per presentare i contenuti. Un modo molto efficace per struttura una pagina simile è di suddividere la logica dalla presentazione, in questo modo:

<?php
try {
  /* (1) logica di business dell'applicazione:
   * - lettura dei parametri;
   * - esecuzione di funzioni;
   * - lettura / scrittura da database;
   * - ecc.
   */
}
catch(Exception $ex) {
  // (2) gestione errori
  $errorMessage = "Si è verificato un errore (" . $ex->getCode() . "):\n" . $ex->getMessage();
  $errorMessage = nl2br(htmlspecialchars($errorMessage, ENT_NOQUOTES, 'UTF-8'));
}
?>
<html>
<head>
  <title>Esempio gestione eccezioni</title>
</head>
<body>
<?php
if (isset($errorMessage)) {
?>
  <div class="errorMessage">
    <?php echo $errorMessage; ?>
  </div>
<?php
}
else {
?>

<h3>Risultato dell'elaborazione</h3>

[...]

<?php
}
?>
</body>

 

In questo modo il codice PHP che si occupa della presentazione verrà eseguito solo se non si sono verificati errori; in caso contrario verrà mostrato un messaggio con i dettagli dell’eccezione verificasi.

Links