Classi

29. Metodi Magici

PHP 5 ci fornisce un supporto aggiuntivo per potenziare le nostre classi attraverso la definizione di determinati metodi detti Metodi Magici, in quanto sono nativi di PHP e non è possibile dichiararne di propri con lo stesso nome (riservati).

Vediamo subito una lista di questi metodi e i servizi che offrono :

  • __toString() - Metodo per restituire l'oggetto sotto forma di stringa
  • __set() - Permette l'overloading per assegnare un valore ad un attributo
  • __get() - Consente l'overloading delle istruzioni di lettura di un attributo
  • __isset() - Overloading del metodo isset() per gli attributi di una classe (disponibile da PHP 5.1.0)
  • __unset() - Overloading del metodo unset() per gli attributi di una classe (disponibile da PHP 5.1.0)
  • __call() - Permette l'overloading dei metodi di una classe
  • __clone() - Consente l'overloading per la clonazione degli oggetti
  • __autoload() - Funzione per l'auto-inclusione di file per classi non ancora definite
  • __sleep() - Richiamato subito prima di una serializzazione di un oggetto
  • __wakeup() - Richiamato subito dopo una serializzazione di un oggetto
  • __set_state() - Metodo statico richiamato per le classi esportate con var_export() (disponibile da PHP 5.1.0)

Overload di __toString()

Prototipo della funzione :

string __toString()

Effettuando l'overloading di questa funzione, consentiremo a PHP 5 di trattare le istanze della nostra classe come stringhe, qualora venissero fatte su di esse operazioni come ad esempio la stampa su schermo con echo o ancora la concatenazione fra stringhe ecc...

Non essendo supportato un tipo di casting implicito da Oggetto a Stringa, questo risulta un ottimo metodo per aggiungere tale supporto alle nostre classi :


<?php

	class Persona
	{
		private $nome;
		private $cognome;

		public function Persona($n, $c)
		{
			$this->nome = $n;
			$this->cognome = $c;
		}

		public function __toString()
		{ return $this->nome . " " . $this->cognome; }
	}

	$obj = new Persona("John", "Doe");
	echo $obj; // Stampa "John Doe"

	$stringa = "Ciao sono " . $obj;
	echo $stringa; // Stampa "Ciao sono John Doe"

?>

Nota : Prima di PHP 5.2.0, __toString() veniva chiamato solo in combinazione con echo e print.

Nei capitoli a seguire vedremo nel dettaglio l'overload degli altri Metodi Magici sopra elencati.

28. Serializzare gli Oggetti

In questo capitolo vedremo come PHP ci consente di serializzare i nostri oggetti.

Serializzare un oggetto significa essenzialmente trasformarlo in una stringa (byte-stream di tutti i valori dell'oggetto) che rappresenterà l'istanza dell'oggetto al suo stato corrente, che potremo poi memorizzare dove preferiamo (es. Database MySQL, file di testo esterno ...).

Per fare questo useremo due funzioni native di PHP 5 : serialize() e la sua antagonista unserialize().

unserialize() ci consentirà di ricostruire l'oggetto che abbiamo precedentemente serializzato, ponendolo all'interno dell'istanza specificata.

Nell'esempio che segue definirò una classe d'esempio ContatoreAccessi, la cui istanza sarà serializzata in un file esterno "statistiche.txt" che conterrà l'oggetto in questione, il cui compito sarà quello di contare quante volte è stata aperta la pagina.

<?php

	class ContatoreAccessi
	{
		private $cont;

		public function MiaClasse($v)
		{ $this->cont = $v; }

		public function stampa()
		{ echo "Accessi : " . $this->cont; }

		public function aumenta()
		{ $this->cont++; }
	}

	$nomefile = "statistiche.txt";
	$dimensione = filesize($nomefile);

	if ($dimensione) // Se il file esiste e contiene più di un byte
	{
		$file = fopen($nomefile, "r"); // Apre il file in sola lettura
		$content = file_get_contents($nomefile); // Legge il contenuto del file e lo memorizza in $content

		$obj = unserialize($content); // Ricostruisce l'istanza deserializzando $content
		$obj->aumenta(); // Incrementa il contatore di accessi
	}
	else
		$obj = new ContatoreAccessi(1);

	$file = fopen($nomefile, "w+"); // Riapre il file in scrittura azzerandone il contenuto

	$ser = serialize($obj); // Serializza l'istanza
	fwrite($file, $ser); // Memorizza l'istanza serializzata
	fclose($file);

	echo $obj->stampa();

?>

Eseguendo il codice soprastante noterete che il contatore di accessi cresce ogni volta che aprite la pagina.
Aprendo "statistiche.txt" col Blocco Note e cancellandone il contenuto, azzererete il contatore di accessi.

Potete memorizzare gli oggetti serializzati ovunque come ad esempio un Database MySQL, mantenendo il preciso stato in cui si trova la vostra applicazione o se preferite parte di essa, notando subito il vantaggio che una volta effettuata la deserializzazione, otterrete immediatamente la vecchia istanza pronta da utilizzare, con i suoi attributi e i suoi metodi.

Abbiate cura che la definizione della classe dell'istanza che andrete a serializzare / deserializzare, sia sempre accessibile dai sorgenti che effettuano questo tipo di operazioni, o otterrete un oggetto inutile.


27. Gestione Avanzata delle Eccezioni

Abbiamo visto nei capitoli precedenti, che l'istruzione throw quando solleva un'eccezione solleva in realtà un oggetto della classe Exception.

PHP ci consente inoltre di sollevare delle eccezioni proprie, attraverso la creazione di sottoclassi specifiche che devono obbligatoriamente essere derivate dalla built-in Exception.

A questo proposito vedremo come sarà possibile annidare più blocchi catch ad un blocco try, similmente ad una struttura di controllo classica come la IF - ELSE IF, che ci consentirà di controllare che tipo di eccezione è stata sollevata e agire di conseguenza.

Illustrerò ora un esempio analogo a quello spiegato nel capitolo Gestione degli Errori, inserendo però una classe aggiuntiva per gestire le eccezioni sugli input utente, lasciando la classe Exception per le altre eccezioni non previste.


Esempio eccezioni personalizzate

Per questo esempio ho preferito creare una classe Utente, che controlla automaticamente la correttezza dei dati, e se necessario solleva un'eccezione del tipo personalizzato ErroriUtente.

Quest'ultima eredita come abbiamo detto prima, dalla classe Exception, fornendo un metodo aggiuntivo per l'inserimento opzionale di un link che invita l'utente a tornare indietro per reinserire i dati.

La funzione "registraUtente()" invece è rimasta invariata, quindi in caso di errore solleva un'eccezione normale che sarà catturata dal secondo blocco catch.

<?php

	class Utente
	{
		public $nome_utente;
		public $pass_utente;

		public function Utente($n, $p)
		{
			if (strlen($n) > ErroriUtente::MAX_LUNGHEZZA)
			    throw new ErroriUtente($n);

			if (strlen($p) > ErroriUtente::MAX_LUNGHEZZA)
			    throw new ErroriUtente($p);

			$this->nome_utente = $n;
			$this->pass_utente = $p;
		}
	}

	class ErroriUtente extends Exception
	{
		const MAX_LUNGHEZZA = 20;

		private $errore;

		public function ErroriUtente($stringa)
		{
			$this->errore = "Errore : La lunghezza massima consentita è " . self::MAX_LUNGHEZZA;
			$this->errore .= "\n<br />\"$stringa\" è di " . strlen($stringa) . " caratteri!";
		}

		private function stampaLink($pagina = "registrazione.php")
		{
			echo "<a href=\"$pagina\">Torna indietro</a> per reinserire i dati.";
		}

		public function stampaMessaggio($link = false)
		{
			echo $this->errore . "<br /><br />\n\n";

			if ($link) $this->stampaLink();
		}
	}
	
	function registraUtente(Utente $utente)
	{
		// ... codice per registrare l'utente nel database

		$registrato = true; // Simuliamo una registrazione avvenuta con successo

		if ($registrato)
			echo "Utente registrato con successo!";
		else
			throw new Exception("Impossibile registrare l'utente, riprova più tardi!");
	}

	try
	{
		$utente = new Utente("Amircare", "supercalifragilistichespiralidoso");

		registraUtente($utente);
	}
	catch (ErroriUtente $e)
	{
		$e->stampaMessaggio(true);
	}
	catch (Exception $e)
	{
		echo $e->getMessage();
	}

?>

Il codice produce questo risultato.


Analisi del codice

Nello script viene sollevata un'eccezione ErroriUtente dal costruttore di Utente nella prima riga del blocco try, che viene catturata dal primo blocco catch.

Se impostate una stringa più corta di "supercalifragilistichespiralidoso" (mannaggia Mary Poppins), e assegnate $registrato a false dentro registraUtente(), allora otterrete il seguente messaggio senza link :
Impossibile registrare l'utente, riprova più tardi!

Quest'ultima eccezione viene invece gestita dal secondo blocco catch.


Suggerimenti sulle eccezioni

Ricordatevi che non sempre è necessario definire un costruttore per la vostra sottoclasse di Exception, se PHP non lo troverà effettuerà automaticamente una chiamata al costruttore della classe madre, ossia Exception.

Inoltre usate questo sistema solo per gestire le eccezioni, per risolvere quindi quei problemi imprevisti che si presume non accadano poi così spesso.

Non utilizzate assolutamente la gestione delle eccezioni come una sruttura di controllo, non solo per un discorso di performance (le eccezioni sono più lente), ma anche per mantenere il codice il più robusto e leggibile possibile, evitando problemi di manutenzione futuri.

Infine prestate attenzione al tipo di eccezioni che gestite in determinati blocchi try / catch, poichè con questo sistema se catturate un eccezione anche molto grave, non interromperete lo script, e il codice successivo sarà quindi eseguito :

<?php

	function sollevaEccezione()
	{ throw new Exception("Eccezione grave!"); }

	function registraUtente()
	{ /* Codice per registrare un'utente */ }

	try
	{
		sollevaEccezione();
	}
	catch (exception $e)
	{
		echo $e->getMessage() . "<br />\n";
	}

	try
	{
		echo "Codice d'esempio che non andrebbe eseguito in caso di eccezioni gravi!";
		registraUtente();
	}
	catch (exception $e)
	{
		echo $e->getMessage();
	}

?>

L'esempio produrrà in output :

Eccezione grave!
Codice d'esempio che non andrebbe eseguito in caso di eccezioni gravi!

E' buona norma quindi minimizzare la quantità di blocchi try / catch e se necessario annidateli.

26. La Classe Exception

La classe Exception è una classe built-in, ossia nativa del linguaggio PHP.

E' possibile estendere questa classe, creandone delle proprie derivate da essa, ma prima di creare le nostre Sottoclassi personalizzate è necessario conoscere Exception e sapere che opportunità ci offre.

Vediamo subito la dichiarazione della classe :

<?php

	class Exception
	{
		protected $message = 'Unknown exception'; // exception message
		protected $code = 0; // user defined exception code
		protected $file; // source filename of exception
		protected $line; // source line of exception

		function __construct($message = null, $code = 0);

		final function getMessage(); // message of exception 
		final function getCode(); // code of exception
		final function getFile(); // source filename
		final function getLine(); // source line
		final function getTrace(); // an array of the backtrace()
		final function getTraceAsString(); // formated string of trace

		/* Overrideable */
		function __toString(); // formated string for display
	}

?>

Diamo un occhiata più da vicino alle proprietà della suddetta classe :

Attributi protetti

  • $message - Il messaggio dell'eccezione
  • $code - Il codice errore definito dall'utente
  • $file - Il file sorgente dove è stata sollevata l'eccezione
  • $line - La riga del sorgente dove è stata sollevata l'eccezione

Costruttore

  • __construct() - Costruttore della classe

Metodi finali

  • getMessage() - Restituisce $message
  • getCode() - Restituisce $code
  • getFile() - Restituisce $file
  • getLine() - Restituisce $line
  • getTrace() - Restituisce un array di backtrace()
  • getTraceAsString() - Restituisce una stringa formattata del trace

Metodi ridefinibili

  • __toString() - Restituisce una stringa formattata dell'oggetto

Nel prossimo capitolo approfondiremo l'argomento creando delle sottoclassi di Exception, per una gestione delle eccezioni maggiormente personalizzata.

25. Gestione degli Errori


Teoria sulle Eccezioni

Come abbiamo visto nel capitolo Strutture di controllo, al verificarsi di una situazione inaspettata, quindi di un errore dal momento che l'applicazione non è stata programmata per produrre quello stato, è possibile interrompere lo script con le istruzioni exit e die.

Così facendo però non si ha una gestione dell'errore intelligente ma una drastica terminazione dello script.

Per essere precisi, il termine esatto per definire questi eventi inaspettati che si verificano durante l'esecuzione dello script, è Eccezioni.

Un evento imprevisto, un baco nello script, la perdita di una connessione al Database, sono tutte considerate eccezioni che PHP ci consente di gestire attraverso tre comandi molto semplici :

  • throw - Solleva un'eccezione
  • try - Se si verifica un'eccezione nel codice del blocco try, quest ultimo fa saltare l'esecuzione del codice al blocco catch
  • catch - Contiene il codice alternativo che viene eseguito al verificarsi dell'eccezione

Il sistema è molto semplice ed è paragonabile ad un normale blocco IF ELSE.

Il codice per cui intendete gestire le eccezioni va all'interno del blocco try, che obbligatoriamente dovrà avere un blocco antagonista catch che conterrà il codice alternativo da eseguire nel caso in cui si verificasse un'eccezione.

Se all'interno del blocco try si verifica una condizione per cui viene chiamato il comando throw, allora l'esecuzione salta al blocco catch.

Non è necessario che l'istruzione throw si trovi esattamente nel blocco try, ma può essere richiamata anche da una funzione o da un metodo di un oggetto all'interno di try.


Esempio di gestione errori

Nell'esempio che segue vedremo del codice che ha come compito il controllo dei dati di un utente.
Se i dati vengono ritenuti adeguati si procede alla registrazione nel database, altrimenti si notifica l'errore all'utente, mettendogli a disposizione un link per tornare indietro a reinserire i dati.

Il codice essendo solo dimostrativo, considera i dati non validi se superano una certa lunghezza, e rimanda l'utente ad una ipotetica pagina di registrazione chiamata registrazione.php :

<?php

	define("MAX_LUNGHEZZA", 20);

	function registraUtente()
	{
		// ... codice per registrare l'utente nel database

		$registrato = true; // Simuliamo una registrazione avvenuta con successo

		if ($registrato)
		    echo "Utente registrato con successo!";
		else
		    throw new Exception("Impossibile registrare l'utente!");
	}

	/* --- Inizio dati Utente --- */

	$nome_utente = "Agamennone";
	$pass_utente = "supercalifragilistichespiralidoso";

	/* --- Fine dati Utente --- */

	try
	{
		if (strlen($nome_utente) > MAX_LUNGHEZZA)
		    throw new Exception("Nome utente troppo lungo!");

		if (strlen($pass_utente) > MAX_LUNGHEZZA)
		    throw new Exception("Password troppo lunga!");

		registraUtente();
	}
	catch (Exception $exc)
	{
		echo "Errore : " . $exc->getMessage() . "<br />\n\n";
		echo "<br /><a href=\"registrazione.php\">Torna indietro</a> per reinserire i dati.";
	}

?>

Il codice soprastante produce questo risultato.

Se provate a modificare la variabile $nome_utente con una stringa più lunga di 20 caratteri, otterrete invece il seguente messaggio :
Errore : Nome utente troppo lungo!

Impostando la variabile $registrato col valore false, all'interno della funzione "registraUtente()", simuliamo un fallimento nella registrazione e viene sollevata una nuova eccezione ottenendo il messaggio :
Errore : Impossibile registrare l'utente!

Ovviamente per far verificare l'ultimo caso descritto, è necessario dare alle due variabili $nome_utente e $pass_utente, una lunghezza minore o uguale a 20, perchè vengono controllate prima dell'esecuzione della funzione "registraUtente()."


Osservazioni sulle eccezioni

Come avrete notato dall'ultimo esempio, quando throw solleva un'eccezione, manda al dovuto blocco catch un'istanza della classe Exception, nativa di PHP.

Throw quindi, utilizza new per creare una nuova istanza, e a seguire il costruttore di Exception con un messaggio opzionale per inizializzarne l'istanza.

La suddetta istanza viene poi catturata da catch, che si serve del metodo "getMessage()" per leggere il messaggio inviatogli da throw e gestire l'eccezione come programmato.

Nota : Se viene sollevata un'eccezione al di fuori di un blocco try / catch, questa interromperà lo script con un errore fatale :
Fatal error: Uncaught exception ...

24. Classi e Metodi final

Come abbiamo visto nei capitoli precedenti, una sottoclasse ha la possibilità di ridefinire i metodi della classe madre.

E' possibile impedire la ridefinizione di determinati metodi e o la derivazione da determinate classi, usando la direttiva final.

Se quindi desiderate che non vengano fornite ulteriori implementazioni di un metodo della vostra classe, allora è sufficiente far precedere alla parola chiave function, la direttiva final in questo modo :

Metodo final

<?php

	class ClasseBase
	{
		public $attributo;

		public final function metodo()
		{
			echo "Sono un metodo finale!";
		}
	}

	class SottoClasse extends ClasseBase
	{
		public function metodo() // Genera un errore fatale
		{
			echo "Proviamo a ridefinire il metodo finale!";
		}
	}

?>

Il codice soprastante genera il seguente errore poichè SottoClasse ha tentato di ridefinire un metodo finale di ClasseBase :
Fatal error: Cannot override final method ClasseBase::metodo()


Se invece volete impedire che una sottoclasse derivi da una classe specifica, applicate a quest'ultima la direttiva final seguita dalla parola chiave class e dal nome classe in questo modo :

Classe final

<?php

	final class ClasseBase
	{
		public $attributo;

		public function metodo()
		{ /* ... */ }
	}

	class SottoClasse extends ClasseBase // Genera un errore fatale
	{
	    /* ... */
	}

?>

Il codice soprastante genera il seguente errore fatale :
Fatal error: Class SottoClasse may not inherit from final class (ClasseBase)

Condividi contenuti