Il nostro cammino verso PHP 5 non è ancora finito. Esistono ancora alcuni elementi che possono aiutarci ad apprezzare al meglio la quinta major release di questo linguaggio.
Dopo aver affrontato gli oggetti con ben 3 interventi ritengo utile completare questa panoramica OOP con un post sulle Eccezioni.
Di PHP 5 ed eccezioni avevo già accennato qualcosa ad inizio anno, pubblicando il post L'arte di gestire le eccezioni, che contiene anche una mini guida introduttiva al significato ed all'utilità di questo elemento di programmazione.
Vediamo, in pratica, l'uso delle eccezioni in PHP 5.
Breve introduzione alle eccezioni
Fermo restando che questo post non vuole essere una guida alle eccezioni in PHP 5, vediamo alcune informazioni base.
L'eccezione, in PHP 5 come nella maggior parte degli altri linguaggi, è rappresentata come un oggetto.
Internamente all'oggetto sono quindi definite delle proprietà , una su tutte il messaggio che illustra il motivo per il quale l'eccezione è stata generata.
Se la dotazione base non ci basta possiamo sempre decidere di estendere il tipo di eccezione che più ci interessa ed aggiungere nuove funzionalità .
Le eccezioni consentono di organizzare in modo più elegante la gestione degli errori di un programma.
Se un errore si verifica è sufficiente lanciare un eccezione e gestirla senza doversi preoccupare di quanti metodi sono attualmente coinvolti nel programma. Questo significa ridurre il numero di controlli di status del tipo
// controlla errori if ($error) then die('Errore, fermati qui'); // fai qualcosa che può generare errori // controlla nuovamente se non ci sono errori if ($error) then die('Errore, avanti non puoi andare'); // prosegui // ultimo controllo errori return $error ? false : true;
La stessa esecuzione può essere progettata con le eccezioni nel seguente modo
try { // fai qualcosa che può generare errori // prosegui // continua // si, dai, procedi ancora } catch (Exception $e) { die('Si è verificato un errore: ' . $e->getMessage()); } return true;
Meglio no?
Gestione di una eccezione
Gestire le eccezioni significa saperle lanciare così come catturare nel modo corretto al fine di istruire l'applicazione per rispondere elegantemente in caso di errore.
Lanciare una eccezione
Per lanciare una eccezione è sufficiente utilizzare il comando throw
seguito da un oggetto di tipo Exception
o una sua sottoclasse.
Ogni eccezione in PHP è infatti una sottoclasse dell'oggetto Exception o un oggetto Exception direttamente.
àˆ possibile allegare ad ogni eccezione un messaggio di errore così come un codice di errore.
// lancio eccezione throw new Exception('Impossibile connettersi al database', '1234'); // creo una nuova eccezione class TestException extends Exception {} // lancio la nuova eccezione, più specifica throw new TestException('Errore test A234');
Ok, ora siamo in grado di lanciare eccezioni ovunque... ma qualcuno dovrà anche raccoglierle, no?!?
Catturare le eccezioni
Catturare una eccezione significa intercettare una eccezione lanciata e gestire il comportamento dell'applicazione in base a quanto accaduto.
Per catturare una eccezione è necessario inserire il codice che potrebbe generarla all'interno di un blocco try {}
al quale far seguire uno o più blocchi catch()
.
try { // codice che può generare eccezione } catch(AnException $e) { // gestione eccezione di tipo AnException e/o sottoclassi } catch(Exception $e) { // gestione eccezione di qualsiasi eccezione // ogni eccezione infatti è sottoclasse di Exception
Il blocco catch()
specifica il tipo di eccezione da catturare seguito da una variabile nella quale memorizzare l'eccezione intercettata.
All'interno del blocco è possibile inserire qualsiasi script si desideri che l'applicazione esegua, compreso il lancio di una ulteriore eccezione. Questo comportamento è molto frequente nel caso di applicazioni complesse composte da molti "livelli".
Ad esempio, potremmo voler stampare un messaggio di errore nel caso in cui si tenti di aprire un file inesistente.
class FileNotFoundException extends Exception {} // recupero il percorso del file $source = isset($_GET['source']) ? trim($_GET['source']) : ''; $target = isset($_GET['target']) ? trim($_GET['target']) : ''; try { if (!file_exists($source)) { throw new FileNotFoundException("Il file sorgente '$source' non esiste"); } if (!file_exists($target)) { throw new FileNotFoundException("Il file destinazione '$target' non esiste"); } // leggi i file $source_content = file_get_contents($source); $target_content = file_get_contents($target); } catch(FileNotFoundException $e) { // fai qualcosa, ad esempio stampa errore echo "Si è verificato un errore: " . $e->getMessage(); }
Questo esempio è banale, ma considerate come avrei dovuto comportarmi se il controllo dell'esistenza dei file e la sua lettura fosse stato in un metodo separato.
Avrei dovuto, ad esempio, fare in modo che il metodo restituisse false in caso di errore, controllare il valore restituito, inserire controlli condizionali e scrivere decine di righe di codice solo per gestire un semplice problema.
In questo modo posso invece limitarmi a scrivere
class FileNotFoundException extends Exception {} // funzione per leggere il file function read_file_if_exists($path) { if (!file_exists($path)) { throw new FileNotFoundException("Il file '$path' non esiste"); } return file_get_contents($path); } // recupero il percorso del file $source = isset($_GET['source']) ? trim($_GET['source']) : ''; $target = isset($_GET['target']) ? trim($_GET['target']) : ''; try { // leggi i file $source_content = read_file_if_exists($source); $target_content = read_file_if_exists($target); } catch(FileNotFoundException $e) { // fai qualcosa, ad esempio stampa errore echo "Si è verificato un errore: " . $e->getMessage(); }
Creare nuove eccezioni
I più attenti tra di voi si saranno accorti che ho già definito negli esempi sopra nuovi tipi di eccezioni.
Definire una nuova eccezione significa, in pratica, estendere la classe base Exception o una sua sottoclasse per creare un tipo di eccezione più specifico per le nostre esigenze.
In questo modo potremo utilizzare catch()
per catturare esattamente l'eccezione voluta e progettare un comportamento specifico.
Vediamo un esempio.
Immaginiamo di avere un'applicazione molto complessa e di voler gestire gli errori come 404 (pagina inesistente), 403 (autorizzazione negata) e 500 (Errore interno) via eccezione.
Potremmo procedere in questo modo.
// creiamo una eccezione per gli errori HTTP class HttpErrorException extends Exception {} // HTTP 4xx class Http4xxException extends HttpErrorException {} class Http404Exception extends Http4xxException {} class Http403Exception extends Http4xxException {} // HTTP 5xx class Http5xxException extends HttpErrorException {} class Http500Exception extends Http4xxException {} // all'interno del nostro controller catturiamo le eccezioni try { // qui inseriamo il normale processo del programma } catch(Http403Exception $e) { header("HTTP/1.0 403 Forbidden"); die('Mica penserai di entrare qui'); } catch(Http404Exception $e) { header("HTTP/1.0 404 Not Found"); die('Hai sbagliato strada'); } catch(Http4xxException $e) { die('Errore 4xx non specificato'); } catch(Http500Exception $e) { header("HTTP/1.0 500 Internal Server Error"); die('Niente panico, stiamo lavorando per voi'); }
Ma poiché le eccezioni sono oggetti potremmo ulteriormente snellire il nostro programma delegando all'eccezione il compito, ad esempio, di impostare gli header HTTP corretti.
// creiamo una eccezione per gli errori HTTP class HttpErrorException extends Exception { protected $_status = null; public function __construct() { parent::__construct(); HttpErrorException::sendHttpHeader($this->_status); } public static function sendHttpHeader($status) { static $headers = array( '403' => 'Forbidden', '404' => 'Not Found', '500' => 'Internal Server Error', ); if (array_key_exists($status, $headers)) { list($code, $message) = $headers[status]; header("HTTP/1.0 $code $message"); } } protected function _setStatus($status) { $this->_status = $status; } } // HTTP 4xx class Http4xxException extends HttpErrorException { function __contruct() { parent::__construct(); $this->setStatus('4xx'); } } class Http404Exception extends Http4xxException { function __contruct() { parent::__construct(); $this->setStatus('404'); } } class Http403Exception extends Http4xxException { function __contruct() { parent::__construct(); $this->setStatus('403'); } } // HTTP 5xx class Http5xxException extends HttpErrorException { function __contruct() { parent::__construct(); $this->setStatus('5xx'); } } class Http500Exception extends Http5xxException { function __contruct() { parent::__construct(); $this->setStatus('500'); } } // all'interno del nostro controller catturiamo le eccezioni // senza inviare ulteriori header try { // qui inseriamo il normale processo del programma } catch(Http403Exception $e) { die('Mica penserai di entrare qui'); } catch(Http404Exception $e) { die('Hai sbagliato strada'); } catch(Http4xxException $e) { die('Errore 4xx non specificato'); } catch(Http500Exception $e) { die('Niente panico, stiamo lavorando per voi'); }
Cosa ne pensate?
In conclusione
Le eccezioni sono indubbiamente una delle caratteristiche più utili nella programmazione ad oggetti... ma non solo!
Come avete visto sopra, parte del codice era procedurale e mi sono permesso ugualmente di snellirlo ricorrendo ad eccezioni.
Siete ancora convinti che PHP 5 non faccia per voi?!?