Lezione Precedente
Lezione Successiva

Laboratorio di Sistemi Informativi

Gestione degli errori

Durante lo sviluppo di una applicazione è importante che vengano visualizzati tutti i messaggi di errore necessari al debugging. Per far questo abbiamo visto come utilizzare i parametri di configurazione display_errors ed error_reporting accessibili dal file .htaccess. Tuttavia, una volta che il sistema va in produzione, lasciare attiva la visualizzazione dei messaggi di errore non è consigliabile, per almeno un paio di motivi:
La soluzione banale di inibire la visualizzazione dei messaggi d'errore con display_errors=0 è possibile ma non consigliabile, in quanto l'utente non viene per nulla a conoscenza di situazioni anomale che si sono verificate.

D'altro canto, durante la fase di sviluppo del codice, vorremmo avere quante più informazioni possibili sull'errore che si è verificato. I messaggi di errore standard di PHP, da questo punto di vista, non brillano particolarmente.

La cosa migliore per risolvere tutti i nostri problemi relativi alla gestione degli errori è scrivere un gestore degli errori personalizzato. Questo sarà l'argomento della lezione di oggi.

Gestore degli errori

Un gestore degli errori è una funzione PHP che ha il compito di gestire tutti (o quasi) gli errori che si verificano durante l'esecuzione del codice PHP. Il gestore di default di PHP visualizza i messaggi di errori sul browser, nel formato che abbiamo più volte visto, purché la variabile di configurazione display_errors sia On. È possibile dire a PHP che si vuole utilizzare un gestore degli errori personalizzato usando il seguente comando:

Quindi, se do il comando set_error_handler("miogestore"), i successivi errori che si genereranno nel codice saranno gestiti dalla funzione miogestore. È importante capire che non misogna mai escplitamente chiamare la funzione miogestore. Sarà l'interprete PHP stesso che la invocherà, con i parametri opportuni, nel caso si verifichi un errore. Ma come va scritta questa funzione?

Un semplicissimo gestore è il seguente:

function miogestore($number, $string, $file, $line, $context)
{
    switch ($number)
    {
        case E_WARNING:
        case E_NOTICE:
            echo "<hr><b>Custom Error Handler -- Warning/Notice</b>";
            echo "<br>Errore in line $line del file $file.\n";
            echo "<br>Errore numero $number : $string\n";
            echo "<br>Alcune informazionid i contesto:\n<pre>\n"
            print_r($context);
            echo "\n</pre>\n<hr>";
            break;
        default:
            // non fa nulla
    }
}

Come si vede la funzione accetta 5 input:
  1. $number indica il tipo di errore (WARNING, ERROR, etc...)
  2. $string è il messaggio di errore
  3. $file è il nome del file in cui l'errore si è verificato
  4. $line è la linea in cui l'errore si è verificato
  5. $context è un array associativo che contiene i valori di tutte le variabili presenti al momento dell'errore. Noi ne stampiamo tutto il contenuto con la funzione print_r.
Abbiamo parlato dei diversi tipi di errore nelle prime lezioni di PHP. Abbiamo parlato di errori di parsing, errori fatali, warning e notice. Ad ognuno di questi tipi (e ad altri ancora) corrisponde un numero intero, che viene passato nel parametro $number.  Eccone alcuni:

Valore Costante Descrizione
1 E_ERROR  Errore fatale.
2 E_WARNING  Warning.
4 E_PARSE  Errore di compilazione.
8 E_NOTICE Notice.
256 E_USER_ERROR Messaggio di errore generato dall'utente. Simile ad E_ERROR, ma è generato dalla funzione trigger_error().
512 E_USER_WARNING  Messaggio di errore generato dall'utente. Simile ad E_WARNING, ma è generato dalla funzione trigger_error().
1024 E_USER_NOTICE Messaggio di errore generato dall'utente. Simile ad E_NOTICE, ma è generato dalla funzione trigger_error().

Non tutti gli errori sono controllabili con un gestore degli errori personalizzato. In particolare, gli errori E_ERROR ed E_PARSE sono sempre gestiti direttamente dal PHP (in quanto si tratta di errori "più gravi" degli altri, che pregiudicano la possibilità di continuare l'esecuzione del codice PHP).

È possibile testare il funzionamento di un gestore personalizzato con lo script error_handler.php

La bufferizzazione degli output

Con la tecnica appena vista, possiamo adattare il gestore degli errori alle nostre esigenze: renderlo "prolisso" quando stiamo eseguendo il debugging, e conciso quando il sistema è in produzione. Tuttavia, questo sistema ha un difetto: l'output del gestore viene mischiato con l'output regolare dello script,  emesso prima che si generi l'errore. Questo può causare vari inconvenienti: l'output può semplicemente essere "poco" elegante, non conforme alle specifiche dell'HTML oppure, in determinate circostanze, sparire del tutto. È quello che succede, per esempio, se l'errore si verifica all'interno di un tag SELECT ma fuori dal tag OPTION.

Quello che vorremmo è che, nel momento in cui si verifichi una situazione di errore, nessun output regolare venga emesso, ma vengano solo prodotti i messaggi generati dal gestore degli errori.

Ciò è possibile , utilizzando le funzioni di controllo dell'output:
L'idea è di chiamare ob_start() all'inizio del programma PHP. Alla fine dello script, se non ci sono stati errori, verrà automaticamente chiamata la funzione ob_end_flush() che produrrà in output la pagina generata. Se invece si verifica un errore, il gestore si occuperà di chiamare ob_end_clean() per eliminare tutto ciò che è stato prodotto fino a quel momento e di visualizzare i messaggi di errore.

Il tutto è esemplificato dallo script error_handler2.php.

Errori generati dall'utente

Vi ricorderete che nella nostra applicazione AirDB abbiamo preso l'abitudine di controllare, dopo ogni invocazione di un comando della libreria MySQL, se si è verificata una situazione di errore. In questo modo siamo in grado di generare messaggi di errore più utili di quelli che verrebbero generati automaticamente da PHP.

Per visualizzare questi errori, abbiamo scritto la funzione mysql_showerror().  Adesso che abbiamo imparato a scrivere un gestore degli errori personalizzato, ci farebbe piacere utilizzare lo stesso meccanismo per visualizzare gli errori di MySQL. A questo scopo possiamo usare la funzione trigger_error:
Chiamare trigger_error invoca il gestore degli errori attivo al momento. Lo script error_handler3.php illustra l'uso di questa funzione.
Per tornare alle nostre applicazioni basate su database è possibile modificare la funzione mysql_showerror sostituendo il codice utilizzato attualmente con quello che segue:
function mysql_showerror()
{
  trigger_error('MySQL Error: '.mysql_error());
}
In questo modo gli errori di MySQL vengono gestiti come gli altri errori di PHP, rendendo più coerente il funzionamento dell'applicazione.

Esercizio 1

Sviluppare un gestore degli errori per l'applicazione AIRdb, che visualizzi un messaggio del tipo "Errore Interno - Contattare l'amministratore del sito web" quando si verifica una situazione anomala. Modificare uno degli script recenti, per esempio prenotazioni.php, per utilizzare questo gestore. Modificare infine la funzione mysql_showerror() in libreria.php in modo che invochi il gestore degli errori con trigger_error invece di visualizzare in maniera autonoma un messaggio di errore.

Esercizio 2

Modificare il gestore degli errori precedente in modo che supporti due modalità di funzionamento: una minimalista come quella proposta nell'esercizio 1 ed una prolissa che visualizzi informazioni aggiuntive utili per il debugging (tipo di errore, linea in cui si è verificato, eccetera). La scelta tra le due modalità di esecuzione deve dipendere dal valore della costante DEBUG, da settare nel file config.php.

Backtracing

Quello che abbiamo visto finora è sufficiente per scrivere un buon gestore degli errori per applicazioni in produzione. Tuttavia, potremmo anche voler scrivere un gestore degli errori che ci faciliti le operazioni di debugging, durante lo sviluppo della applicazione. A questo scopo, abbiamo visto come utilizzare la variabile $context per visualizzare il valore di tutte le variabili note al momento dell'errore. Una ulteriore possibilità ci è offerta dalle seguenti funzioni: Il risultato di debug_backtrace è un array con l'elenco di tutte le funzioni che sono state chiamate, e la cui esecuzione non è ancora conclusa, al momento in cui debug_backtrace stessa viene invocata.

Ogni posizione dell'array contiene informazioni su una chiamata di funzione, partendo dalla posizione 0 (l'ultima funzione chiamata), via via fino alle più remote. Inoltre, ogni posizione dell'array è a sua volta un array associativo, con i seguenti campi:

Nome Tipo Descrizione
function string Il nome della funzione chiamata
line integer La linea in cui si trova la chiamata di funzione.
file string Il nome del file in cui si trova la chiamata di funzione.
class string Il nome della classe in cui si trova la chiamata di funzione.. non ce ne occupiamo perchè non abbiamo visto la gestione degli oggetti in PHP
type string Il tipo di chiamata (significativo solo per chiamate di metodi..)
args array L'elenco degli argomenti passati alla funzione

Si può vedere un esempio dell'uso della funzione debug_backtrace() nello script backtrace_example.php. Piuttosto che visualizzare l'output di debug_backtrace() con la funzione print_r, è più conveniente chiamare direttamente la funzione debug_print_backtrace.

L'idea è di integrare la funzione debug_backtrace() all'interno del nostro gestore degli errori personalizzato, per fornire al programmatore, nel momento in cui si verifica un errore, una ampia dose di informazioni. È quello che abbiamo realizzato in error_handler4.php. Abbiamo utilizzato in questo script la funzione backtrace(), scopiazzata da Internet, per visualizzare in maniera "elegante" il risultato di debug_backtrace(), ancora meglio di come sia possibile con debug_print_backtrace().
ATTENZIONE. Purtroppo nella Fedora 4 c'è un bug in PHP che rende difficilmente interpretabili i backtrace ottenuti all'interno di un gestore degli errori. Si consiglia quindi questo utilizzo solo con altre distribuzioni di Linux (Fedora 5 e Ubuntu 5.10 sono OK).

Esercizio 3

Modificare il gestore degli errori dell'esercizio precedente aggiungendo la visualizzazione del backtrace, nel caso sia attiva la modalità prolissa.

Lezione Precedente
Lezione Successiva

I file utili per questa lezione: .htaccess