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:
- confondono l'utente medio dell'applicazione;
- forniscono informazioni preziose sulla struttura interna
dell'applicazione ad un eventuale cracker.
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:
- string set_error_handler (string
error_handler): prende come input il nome di una funzione
in PHP, e setta il gestore degli errori a tale funzione.
Restituisce il nome del vecchio gestore degli errori, che pertanto
può essere ripristinato con una successiva chiamata a set_error_handler, oppure false in caso di errore.
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:
- $number indica il
tipo di errore (WARNING, ERROR, etc...)
- $string è il
messaggio di errore
- $file è il
nome del file in cui l'errore si è verificato
- $line è la
linea in cui l'errore si è verificato
- $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:
- bool ob_start (void): attiva la
bufferizzazione dell'output. Da quel momento in poi, l'output non viene
inviato al browser, ma viene conservato in un buffer in memoria.
Restituisce true se tutto
è ok. (In realtà, la funzione ha vari parametri opzionali
che qui ignoriamo)
- bool ob_end_flush (void): manda al browser tutto
ciò che è attualmente nel buffer di output e termina la
bufferizzazione. È automaticamente chiamata alla fine dello script PHP.
- bool ob_end_clean (void): distrugge tutto quello
che è attualmente nel buffer, senza mandarlo al browser, e
termina la bufferizzazione.
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:
- bool trigger_error(string error_msg [, int error_type]):
genera un errore di tipo error_type
e il cui contenuto è error_msg.
Il parametro error_type può essere uno tra E_USER_ERROR, E_USER_NOTICE ed E_USER_WARNING. Il default
è E_USER_NOTICE.
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:
- array debug_backtrace (void):
restituisce un array contenente le informazioni di backtracing;
void debug_print_backtrace (void)
: manda in output le informazioni di backtrace corrente opportunamente formattate.
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.
I file utili per questa lezione: .htaccess