Classi

49. Classi per inviare email

In questo capitolo costruiremo una libreria che ci consentirà di inviare email complesse, ad esempio in HTML con uno o più allegati, in modo molto trasparente e veloce.

Questa libreria non è completa anche se funziona correttamente. Non ho infatti implementato alcuna gestione delle eccezioni che vi consiglio pertanto di aggiungere qualora decidiate di usarla nello sviluppo di applicazioni reali.
Inoltre sarebbe stato utile interfacciare la libreria con la classe "File" vista nei capitoli precedenti, ma ho preferito comunque non inserire altre classi esterne essendo lo scopo di questa libreria puramente didattico.

Vediamo ora le Classi che compongono questa nuova libreria e il loro scopo :

  • MIME - Classe usata come contenitore di costanti per definire i tipi MIME supportati dall'applicazione
  • MailBlock - Rappresenta un sottoblocco del messaggio email, quest ultimo sarà quindi composto da una o più istanze di MailBlock
  • Allegato - Derivata da MailBlock, rappresenta un file allegato ossia un sottoblocco più complesso con codifica Base64
  • Email - Classe principale creata per fornire un'interfaccia fra tutte le altre classi, costruendo il messaggio email completo e inviandolo

Classe : "MIME"
Nome file : "MIME.php"

<?php

	class MIME
	{
		const HTML = "text/html";
		const TEXT = "text/plain";
		const MULTI = "multipart/mixed";

		const GIF = "image/gif";
		const JPEG = "image/jpeg";
		const PNG = "image/png";
		const PSD = "image/psd";
		const BMP = "image/bmp";
		const TIFF = "image/tiff";
		const FLASH = "application/x-shockwave-flash";
	}

?>

Classe : "MailBlock"
Nome file : "MailBlock.php"

<?php

	define("EOL", "\r\n");

	class MailBlock
	{
		protected $content_type;
		protected $charset;
		protected $content_transfer_encoding;

		public $content;
		protected $boundary;

		public function MailBlock($content_type, $boundary, $content, $charset = "iso-8859-1", $c_t_encoding = "8bit")
		{
			$this->content_type = $content_type;
			$this->charset = $charset;
			$this->content_transfer_encoding = $c_t_encoding;

			$this->content = $content;
			$this->boundary = $boundary;
		}

		public function __toString()
		{
			$content = "--" . $this->boundary . EOL;
			$content .= "Content-Type: " . $this->content_type . "; charset=" . $this->charset . EOL;
			$content .= "Content-Transfer-Encoding: " . $this->content_transfer_encoding . EOL;
			$content .= $this->content . EOL;
			$content .= "--" . $this->boundary . EOL;

			return $content;
		}
	}

?>

La classe proposta è piuttosto semplice, non fa altro che collezionare i dati necessari a rappresentare un sottoblocco e ad organizzarli attraverso il metodo __toString().
Il codice che segue inizializza correttamente un sottoblocco in formato HTML :

<?php

	require_once("MIME.php");
	require_once("MailBlock.php");

	$boundary = "fa0s7u98hg87ngf0hgk05695j";
	$contenuto = "<ul><li>Linea uno</li><li>Linea due</li></ul>";

	$blocco = new MailBlock(MIME::HTML, $boundary, $contenuto);

	echo $blocco; // Richiama "__toString()" stampando il sottoblocco con la sintassi corretta

?>

Classe : "Allegato"
Nome file : "Allegato.php"

<?php

	require_once("MailBlock.php");

	class Allegato extends MailBlock
	{
		public $url;
		public $name;
		public $description;

		public function Allegato($name, $url, $content_type, $boundary, $description = NULL)
		{
			$this->url = $url;
			$this->name = $name;
			$this->boundary = $boundary;
			$this->description = $description;

			$this->content = $this->leggi();
			parent::MailBlock($content_type, $boundary, $this->content, "utf-8", "base64");
		}

		private function leggi()
		{
			$file = @fopen($this->url, "r");
			$allegato = fread($file, filesize($this->url));
			return base64_encode($allegato);
		}

		public function __toString()
		{
			$content = "--" . $this->boundary . EOL;
			$content .= "Content-Type: " . $this->content_type . "; name=\"" . $this->name . "\"" . EOL;
			$content .= "Content-Transfer-Encoding: " . $this->content_transfer_encoding . EOL;
			$content .= "Content-Description: " . $this->description . EOL;
			$content .= "Content-Disposition: attachment; filename=\"" . $this->name . "\"" . EOL;
			$content .= $this->content . EOL;
			$content .= "--" . $this->boundary . EOL;

			return $content;
		}
	}

?>

La classe soprastante deriva dalla precendente MailBlock, aggiungendo però tre importanti parametri necessari per gestire il file allegato, ossia l'url, il nome del file che sarà visualizzato dal destinatario e infine una descrizione facoltativa.

Il metodo privato leggi() si occupa invece di leggere il file specificato, per poi inserirlo nel sottoblocco in questione usando la codifica Base64.

Il metodo pubblico __toString() si comporta come il suo genitore, organizzando però il sottoblocco con i tre parametri aggiuntivi e inserendo alla fine il contenuto del file specificato.

Nel prossimo capitolo vedremo l'oggetto Email, l'ultima classe della nostra libreria con del codice d'esempio.

In questa lezione vedremo la classe Email, analizzandone brevemente metodi e attributi ed infine un esempio per l'utilizzo della nuova libreria con l'invio di due allegati.

Attributi

  • private $to_mail - Array contenente la lista dei destinatari.
  • private $object - Oggetto dell'email.
  • private $message - Array contenente tutti i sottoblocchi che formeranno il messaggio, ossia istanze di "MailBlock" e "Allegato".
  • public $mime - Versione MIME utilizzata per lo standard dell'email.
  • public $content_type - Content-Type per identificare il contenuto del messaggio (es. "text/html" oppure "multipart/mixed" ...).
  • private $boundary - Delimitatore boundary generato criptando il timestamp Unix corrente.
  • public $cc - Stringa contenente gli indirizzi per l'invio dell'email in copia carbone. Consiglio di rendere private l'attributo e di trasformarlo in un array, costruendo un metodo per un inserimento controllato.
  • public $bcc - Stringa contenente gli indirizzi per l'invio dell'email in copia carbone. Anche qui consiglio di agire come con l'attributo $cc.
  • public $date - Data di invio email (tipo stringa).
  • public $from - Indirizzo email del mittente, consiglio anche qui di effettuare la stessa modifica descritta per l'attributo $cc.
  • public $reply_to - Specifica l'email predefinita a cui il destinatario dell'email potrà rispondere. Anche qui stesso consiglio.
  • public $xmailer - Stringa che identifica il software utilizzato per l'invio dell'email.

Metodi

  • public Email() - Costruttore della classe. Inizializza $object, $content_type e altri attributi.
  • public blocco() - Aggiunge un sottoblocco standard al corpo del messaggio, ossia un'istanza MailBlock.
  • public allegato() - Aggiunge un sottoblocco più complesso al corpo del messaggio che rappresenterà un file allegato, ossia un'istanza di Allegato.
  • public destinatario() - Aggiunge un indirizzo alla lista dei destinatari.
  • private header() - Metodo privato che costruisce l'header dell'email basandosi sui parametri memorizzati negli attributi della classe.
  • public invia() - Metodo per l'invio dell'email. Restituisce true o false per identificare se l'invio è avvenuto con successo.

Classe : "Email"
Nome file : "Email.php"

<?php

	require_once("MIME.php");
	require_once("Allegato.php");

	class Email
	{
		private $to_mail;
		private $object;
		private $message;

		public $mime = "1.0";
		public $content_type;
		private $boundary = NULL;

		public $cc = NULL;
		public $bcc = NULL;
		public $date = NULL;
		public $from = NULL;
		public $replyto = NULL;
		public $xmailer = NULL;

		public function Email($object, $content_type = MIME::TEXT)
		{
			$this->to_mail = array();
			$this->object = $object;
			$this->message = array();

			$this->boundary = md5(time());
			$this->content_type = $content_type;
		}

		public function blocco($content_type, $content, $charset = "iso-8859-1", $c_t_encoding = "8bit")
		{
			$succ = count($this->message);
			$this->message[$succ] = new MailBlock($content_type, $this->boundary, $content, $charset, $c_t_encoding);
		}

		public function allegato($name, $url, $mime_type, $description = NULL)
		{
			$succ = count($this->message);
			$this->message[$succ] = new Allegato($name, $url, $mime_type, $this->boundary, $description);

			/* Se questo metodo viene richiamato significa che è stato inserito almeno un
			allegato quindi per sicurezza modifico il Content-Type a "multipart/mixed" */
			$this->content_type = MIME::MULTI;
		}

		public function destinatario($to_mail)
		{
			array_push($this->to_mail, $to_mail);
		}

		private function header()
		{
			$header = "MIME-Version: " . $this->mime . EOL;
			$header .= "Content-Type: " . $this->content_type . "; boundary=\"" . $this->boundary . "\"" . EOL;
			$header .= "Content-Transfer-Encoding: 8bit" . EOL;

			if ($this->from != NULL) { $header .= "From: " . $this->from . EOL; }
			if ($this->replyto != NULL) { $header .= "Reply-To: " . $this->replyto . EOL; }
			if ($this->cc != NULL) { $header .= "Cc: " . $this->cc . EOL; }
			if ($this->bcc != NULL) { $header .= "Bcc: " . $this->bcc . EOL; }
			if ($this->date != NULL) { $header .= "Date: " . $this->date . EOL; }
			if ($this->xmailer != NULL) { $header .= "X-Mailer: " . $this->xmailer; }

			return $header;
		}

		public function invia()
		{
			$message = "";
			$blocchi = count($this->message);

			for ($i = 0; $i < $blocchi; $i++)
				$message .= $this->message[$i]; // Richiama il metodo __toString() di "Allegato" o "MailBlock"

			$to = implode(", ", $this->to_mail);
			return mail($to, $this->object, $message, $this->header());
		}
	}

?>

Come potete vedere anche la classe Email è piuttosto semplice dovendo principalmente fungere da interfaccia unendo i dati.

Con questa classe la nostra libreria è al completo. Per ultimo vedremo un piccolo foglio PHP come esempio di utilizzo della libreria, inviando un'email con un blocco di testo, un blocco in HTML e 2 file allegati.

test.php

<?php

	require_once("Email.php");

	$mail = new Email("Mail con allegati", MIME::MULTI);

	/* Codice per aggiungere i destinatari */
	$mail->destinatario("RS Staff <posta@realizzazione-sito.info>");
	//$mail->destinatario("Utente1 <utente1@dominio.it>");
	//$mail->destinatario("Utente2 <utente2@dominio.it>");

	$mail->from = "Mittente <email_di_invio@gmail.com>";
	$mail->replyto = "Risposta <email_per_risposta@gmail.com>";

	$mail->blocco(MIME::TEXT, "Iniziamo con un pò di testo nel primo blocco!!");

	/* Memorizzo l'html per il secondo blocco in una
	variabile per favorire la leggibilità del codice */
	$html = "<ul><li>Linea uno</li><li>Linea due</li></ul>";
	$html .= "<table border=\"1\"><tr><td>topolino<td><td>minnie</td></tr></table>";

	$mail->blocco(MIME::HTML, $html);
	$mail->allegato("litfiba.jpg", "allegati/file.jpg", MIME::JPEG);
	$mail->allegato("220 volts.jpg", "allegati/220v.jpg", MIME::JPEG);

	$inviata = $mail->invia();

	if ($inviata) { echo "Mail inviata con successo!!"; }
	else { echo "Mail non inviata!!"; }

?>

Il risultato ottenuto.

Anche in questo caso ho testato il codice solo con GMail, non escludo quindi problemi di compatibilità con altri siti o client di posta (Outlook, Eudora ...), e dovendo riscontrarne vi consiglio di provare a modificare l'EOL o il boundary di chiusura.

46. Classi per l'upload di file

In questo capitolo vedremo un esempio più complesso del precedente, ma che ci fornirà un'interfaccia più intuitiva e sicura per gestire l'upload dei file sui nostri server.

Per questo scopo ho creato due classi a cui vi consiglio più di applicare i concetti di ereditarietà e polimorfismo per creare poi le vostre classi per una libreria personalizzata più adeguata alle esigenze della vostra applicazione.

Prima di introdurre il codice della libreria è necessario sapere che esiste uno specifico tipo MIME per ogni tipo di file inviato.

Come abbiamo visto nel capitolo precedente, una volta inviato il file questo è accessibile attraverso l'array globale $_FILES[], in particolare con la chiave "type" è possibile accedere alla stringa che descrive che tipo di file è stato inviato.

Se "type" è uguale a "image/jpeg" abbiamo ricevuto una jpeg, se è uguale a "audio/x-mpeg-3" abbiamo ricevuto un file mp3 e così via ...
Una lista dettagliata dei tipi di file con i rispettivi MIME.

Iniziamo dalla classe "ErroreFile" per una gestione mirata delle eccezioni, che si appoggerà alle costanti globali degli errori di upload che abbiamo già visto nel capitolo precedente.

Nella libreria che segue ho definito inoltre altre costanti per gestire delle eccezioni non previste dal set di costanti globali.

Lo scopo principale della classe che segue è quello di fornire un set di messaggi d'errore tradotti in italiano, attraverso un array statico. Derivando da questa classe potrete quindi fornire dei messaggi in altre lingue o messaggi di errore aggiuntivi per gestire eccezioni particolari, personalizzate per le vostre applicazioni.

ErroreFile.php
<?php

	define("UPLOAD_ERR_MOVE", 7);
	define("UPLOAD_ERR_NO_FORM", 8);
	define("UPLOAD_ERR_NO_TYPE", 9);
	define("UPLOAD_ERR_UNKNOWN", 10);

	class ErroreFile extends Exception
	{
		private $errore;
		static $tipo = array(
			UPLOAD_ERR_OK => "File inviato con successo",
			UPLOAD_ERR_INI_SIZE => "Dimensione del file troppo grande",
			UPLOAD_ERR_FORM_SIZE => "Dimensione del file troppo grande",
			UPLOAD_ERR_PARTIAL => "File ricevuto parzialmente",
			UPLOAD_ERR_NO_FILE => "Nessun file ricevuto",
			UPLOAD_ERR_NO_TMP_DIR => "Cartella temporanea non trovata",
			UPLOAD_ERR_MOVE => "Impossibile archiviare il file",
			UPLOAD_ERR_NO_FORM => "Nessun file specificato",
			UPLOAD_ERR_NO_TYPE => "Formato file non consentito",
			UPLOAD_ERR_UNKNOWN => "Errore sconosciuto"

		);

		public function ErroreFile($err_no)
		{ $this->errore = (int) $err_no; }

		public function messaggio()
		{
			foreach (ErroreFile::$tipo as $chiave => $valore)
				if ($this->errore == $chiave)
					return $valore;

			return ErroreFile::$tipo[UPLOAD_ERR_UNKNOWN];
		}
	}

?>

Ho aggiunto 4 costanti per descrivere altri tipi di eccezioni che potrebbero verificarsi, facendo attenzione a non utilizzare i codici già presi dalle altre costanti predefinite, ossia 0, 1, 2, 3, 4 e 6 (non chiedetemi che fine ha fatto il 5).

Vediamo ora la classe File che si occuperà di gestire il file inviato e di controllare che sia compatibile con le nostre esigenze.


Classe File

A inizio sorgente dichiarerò anche qui un set di costanti globali, per non dover ricordare il tipo MIME dei file che consideriamo validi per la nostra applicazione. Per il mio esempio ho scelto questi :

  • GIF - "image/gif"
  • JPEG - "image/jpeg"
  • PNG - "image/png"
  • PSD - "image/psd"
  • BMP - "image/bmp"
  • TIFF - "image/tiff"
  • FLASH - "application/x-shockwave-flash"

Un'occhiata veloce agli attributi e ai metodi della classe File :

  • private $file - L'array che conterrà solo il file specificato
  • private $cartella - La cartella remota dove andrà memorizzato il file inviato
  • public function File() - Il costruttore della classe che prenderà in input il nome del file da gestire e la cartella di destinazione
  • public function tipo() - Metodo che prende un numero di parametri variabile, ossia tutti i tipi di file che consideriamo validi
  • public function dimensione() - La dimensione massima accettata per il file inviato. Prende due parametri, il primo la dimensione massima e il secondo l'unità di misura per valutare la dimensione, ossia "byte", "kb" o "mb" (default "byte")
  • public function sposta() - Sposta il file nella cartella di destinazione
  • public function immagine() - Restituisce la stringa già formattata in HTML per visualizzare l'immagine inviata
    Prima di usare verificare che il file sia un'immagine con il metodo tipo()
  • public function __get() - Restituisce il valore dell'attributo specificato
  • public function __toString() - Restituisce il nome originale del file
File.php
<?php

	require_once("ErroreFile.php");

	// MIME Types
	define("GIF", "image/gif");
	define("JPEG", "image/jpeg");
	define("PNG", "image/png");
	define("PSD", "image/psd");
	define("BMP", "image/bmp");
	define("TIFF", "image/tiff");
	define("FLASH", "application/x-shockwave-flash"); // Filmato Adobe Flash

	class File
	{
		private $file;
		private $cartella;

		public function File($nome, $cartella = "/uploads/")
		{
			if (isset($_FILES[$nome]))
			{
				$this->file = $_FILES[$nome];
				$this->cartella = $cartella;
			}
			else
				throw new ErroreFile(UPLOAD_ERR_NO_FORM);
		}

		public function tipo()
		{
			if (!func_num_args()) throw new ErroreFile(UPLOAD_ERR_NO_TYPE);

			$tipi = func_get_args();

			foreach ($tipi as $chiave => $valore)
				if ($this->file["type"] == $valore)
					return true;

			throw new ErroreFile(UPLOAD_ERR_NO_TYPE);
		}

		public function dimensione($dim, $unita = "byte")
		{
			if (strcasecmp($unita, "byte") == 0) $dim = (int) $dim;
			else if (strcasecmp($unita, "kb") == 0) $dim = (int) $dim * 1024;
			else if (strcasecmp($unita, "mb") == 0) $dim = (int) $dim * 1024 * 1024;
			else $dim = 0;

			if ($this->file["size"] > $dim) throw new ErroreFile(UPLOAD_ERR_FORM_SIZE);
		}

		public function sposta()
		{
			$ok = @move_uploaded_file($this->file["tmp_name"], $this->cartella . $this->file["name"]);

			if (!$ok) throw new ErroreFile(UPLOAD_ERR_MOVE);
		}

		public function immagine($dir, $alt = NULL)
		{
			$dimensioni = getimagesize($dir . $this->file["name"]);
			$dimensioni = $dimensioni[3]; // height="yyy" width="xxx"

			
			$imgtag = "<img src=\"$dir" . $this->file["name"] . "\" ";
			$imgtag .= "$dimensioni ";
			$imgtag .= (strlen($alt) ? "alt=\"$alt\"" : "") . " />"; // Imposta l'attributo ALT per l'immagine

			return $imgtag;
		}

		public function __get($attributo)
		{
			if (array_key_exists($attributo, $this->file))
				return $this->file[$attributo];

			return false;
		}

		public function __toString()
		{ return $this->file["name"]; }
	}

?>

Per l'esempio ho utilizzato alcune funzioni native che non avevo ancora spiegato, di seguito i nomi e la relativa documentazione :

A seguire la pagina upload.php dell'esempio precedente, riscritta per l'uso delle nuove classi.

upload.php

<?php

	require_once("File.php");

	try
	{
		$file = new File("immagine", "C:/Appserv/www/test/uploads/");
		$file->tipo(JPEG, PNG); // Accettate solo immagini in formato JPEG o PNG
		$file->dimensione(100, "kb"); // Solo immagini più piccole di 100Kb
		$file->sposta(); // Sposta il file inviato nella cartella di destinazione

		echo "Nome file : $file<br /><br />";
		echo $file->immagine("uploads/", "Immagine inviata"); // Visualizza l'immagine appena inviata
	}
	catch (ErroreFile $e)
	{ echo $e->messaggio(); }
	catch (Exception $e)
	{ echo $e->getMessage(); }

?>

Il risultato ottenuto inviando un'immagine che soddisfa i parametri dell'esempio.

42. Una classe per i Cookie

In questo capitolo extra, ripeterò l'esempio del capitolo precedente utilizzando la programmazione orientata agli oggetti (OOP).

Ho scritto una classe Cookie molto semplice ma che ci consente di trarre dei vantaggi che non avremmo con la sola funzione nativa setcookie.


Classe Cookie

Attributi :

  • name - Un nome per il cookie
  • value - Il valore da registrare nel cookie
  • expire - La durata del cookie espressa in secondi
  • path - Path del sito da cui è accessibile il cookie
  • domain - Dominio web da cui è accessibile il cookie
  • secure - Per impostare il cookie solo su connessioni sicure HTTPS
  • httponly - Per rendere accessibile il cookie solo attraverso il protocollo HTTP

Come per setcookie(), solo name è obbligatorio mentre tutti gli altri attributi sono facoltativi.

Metodi :

  • setcookie() - Metodo per impostare il cookie
  • getPath() - Metodo statico che restituisce la path dello script attualmente in esecuzione
  • unserialize() - Metodo statico per recuperare da un cookie l'istanza originale della classe Cookie
  • cancella() - Metodo statico per cancellare un cookie
  • __toString() - Metodo che restituisce direttamente il valore del cookie se questo viene usato come stringa

Listato della classe Cookie

Introduco ora il codice della classe Cookie che risiede in un file esterno nominato Cookie.php :

<?php

	class Cookie
	{
		public $name;
		public $value;
		public $expire;
		public $path;
		public $domain;
		public $secure;
		public $httponly;

		public function Cookie($n, $v = "", $e = 0, $p = NULL, $d = NULL, $s = false, $h = false)
		{
			$this->name = $n;
			$this->value = $v;
			$this->expire = ($e == 0) ? $e : (int) ($e * 60 * 60) + time();
			$this->path = ($p == NULL) ? Cookie::getPath() : $p;
			$this->domain = $d;
			$this->secure = $s;
			$this->httponly = $h;
		}

		public function setcookie($salva_tutto = false)
		{
			$cookie = ($salva_tutto) ? serialize($this) : $this->value;

			$_COOKIE[$this->name] = $cookie;
			setcookie($this->name, $cookie, $this->expire, $this->path,
			$this->domain, $this->secure, $this->httponly);
		}

		static public function getPath()
		{
			$cartelle = explode("/", $_SERVER["PHP_SELF"]);

			$path = "";
			$dimensione = count($cartelle) - 1;

			for ($i = 0; $i < $dimensione; $i  )
				$path .= $cartelle[$i] . "/";

			return $path;
		}

		static public function unserialize($nomeCookie)
		{
			$cookie = unserialize(stripslashes($_COOKIE[$nomeCookie]));

			if ($cookie instanceof Cookie)
				return $cookie;
			else
				throw new Exception("Cookie corrotto");
		}

		static public function cancella($nomeCookie)
		{
			unset($_COOKIE[$nomeCookie]);
			setcookie($nomeCookie, "", time() - 86400); // Imposta la scadenza al giorno prima
		}

		public function __toString()
		{
			return $this->value;
		}
	}

?>

Vantaggi della classe Cookie

Uno dei vantaggi di questa classe è quello di poter usare solo i parametri che ci interessano realmente, senza obbligatoriamente specificare anche tutti gli altri come avviene con setcookie().

Se ad esempio volessimo specificare solo il nome del cookie, un valore e impostare a true l'opzione httponly sarebbe sufficiente utilizzare il seguente codice :

<?php

	require_once("Cookie.php");
	
	$cookie = new Cookie("nome cookie", "valore del cookie");
	$cookie->httponly = true;
	$cookie->setcookie();

?>

Con setcookie() invece avremmo dovuto specificare per forza anche expire, path, domain e secure per poter arrivare ad httponly rendendo il codice molto meno intuitivo e leggibile. La variante che segue è equivalente al codice soprastante :

<?php

	setcookie("nome cookie", "valore del cookie", 0, "/", NULL, false, true);

?>

Altra particolarità di questa classe è che ci consente di specificare come scadenza direttamente il numero di ore a partire dal momento in cui viene impostato il cookie. Potete facilmente modificare la classe, ad esempio per considerare expire come il numero di giorni di vita del cookie e così via :

<?php

	$cookie = new Cookie("nome cookie", "valore del cookie", 24); // Dura 24 ore
	$cookie->httponly = true;
	$cookie->setcookie();

	// oppure

	setcookie("nome cookie", "valore del cookie", time()   (60 * 60 * 24), "/", NULL, false, true);

?>

Se un'istanza di Cookie viene stampata o concatenata come stringa, interviene il metodo magico __toString() restituendo direttamente il valore memorizzato nel cookie.

Punto di forza di questa classe è il metodo pubblico setcookie(), che come avrete notato prende un parametro $salva_tutto impostato di default al valore false.

Se richiamando la funzione lo impostate invece a true, il metodo setcookie() serializza l'oggetto e lo salva integro nel cookie.

Sarà poi possibile recuperare l'oggetto salvato con il metodo statico unserialize, permettendoci di leggere anche in un secondo momento tutte le proprietà del cookie, cosa impossibile se avessimo usato solo la funzione nativa setcookie().


Esempio classe Cookie

Di seguito riporto l'esempio del capitolo precedente riscritto per la classe Cookie con la serializzazione dell'oggetto ...

classi_cookie.php

<?php

	require_once("Cookie.php");

	if ($_POST["comando"] == "cancella")
	{
		Cookie::cancella("dato");
	}
	else if ($_POST["comando"] == "imposta")
	{
		$cookie = new Cookie("dato", $_POST["dato"], $_POST["expire"]);
		$cookie->setcookie(true);
	}

?>

<html>
	<head>
		<title>Impostiamo e cancelliamo i Cookie</title>
	</head>
	<body>

		<?php

			if (isset($_COOKIE["dato"]))
			{
				try
				{
					$cookie = Cookie::unserialize("dato");

					echo "Il Cookie \"$cookie->name\" ha il valore = \"$cookie->value\"";
					echo "<br />\nScade in data " . date("d/m/Y - H:i:s", $cookie->expire);
					echo "<br />\nE' accessibile dal dominio \"" . (($cookie->domain) ? $cookie->domain : "default") . "\"";
					echo " per la path \"$cookie->path\"";
					echo "<br />\nsecure = \"" . (($cookie->secure) ? "true" : "false") . "\"";
					echo "<br />\nhttponly = \"" . (($cookie->httponly) ? "true" : "false") . "\"";
				}
				catch (Exception $e)
				{
					echo "<h3>" . $e->getMessage() . "</h3>";
				}

				?>

				<br /><br />

				<form name="deletecookie" action="classi_cookie.php" method="post">
					<input type="hidden" name="comando" value="cancella" />

					<input type="submit" value="Cancella cookie" />
				</form>

				<?php
			}
			else
			{
				?>

				<form name="writecookie" action="classi_cookie.php" method="post">
					<table width="400" border="0" cellspacing="5" cellpadding="5">

						<tr>
							<td>Dato da memorizzare</td>
							<td><input type="text" name="dato" value="" /></td>

						</tr>
						<tr>
							<td>Scadenza del cookie</td>
							<td><input type="text" name="expire" value="" /></td>

						</tr>
						<tr>
							<td><input type="submit" value="Imposta cookie" /></td>

							<td> </td>
						</tr>
					</table>
					<input type="hidden" name="comando" value="imposta" />

				</form>

				<?php
			}
		
		?>

	</body>
</html>

Una volta impostato il cookie si ottiene questo output.


38. Osservazioni finali sugli Oggetti

Con questo capitolo concludo tutta la parte che riguarda la struttura e la sintassi del linguaggio, per avvicinarmi a un livello più avanzato di questa guida, dove inizierò ad illustrare i metodi e gli strumenti che PHP 5 ci fornisce per realizzare delle vere applicazioni web, per interagire con l'utente e scambiare dati e molto altro.

Prima di proseguire volevo darvi qualche consiglio su come usare al meglio quello che avete imparato finora.

Se non avete mai programmato ad Oggetti e siete ancora un pò restii a farlo, vi assicuro che è solo questione di pratica e vi troverete poi dannatamente male scontrandovi con altri linguaggi che non sono invece Object Oriented.

Questa strategia di programmazione è alla base di molti linguaggi evoluti odierni, e vi permetterà di risparmiare tonnellate di codice e di tempo ... provare per credere.

Vorrei aggiungere inoltre, che molti troveranno scomoda la decisione di PHP di non essere molto tipizzato come ad esempio il C++.

Per molti altri invece potrà essere un vantaggio, io in ogni caso vi consiglio di fare molta attenzione quando sviluppate i vostri script, facendo largo uso dell'operatore instanceof e specificando ogni volta che vi sarà possibile il tipo di Classe usata nei parametri delle funzioni, sfruttando il più possibile il Polimorfismo, per essere sempre certi che la vostra applicazione stia lavorando sulle classi giuste.

<?php

	$obj = new MioOggetto();

	if ($obj instanceof MioOggetto)
		// continua è tutto ok
	else throw new Exception("Tipo di dato non corretto");

?>

Usate sempre try e catch per gestire le eccezioni, ma usateli solo per le eccezioni appunto e mai come struttura di controllo.

Se siete capitati qui per caso, perchè avete cercato la frase sbagliata su Google vi consiglio di dare una letta anche al resto :-P

Non avete idea (o magari si LOL) di quanta gente che si dice esperta di IT non sa neanche fare una ricerca corretta su Internet.
Perdonate il fuori tema, ma visto che ci sono vi informo che le ricerche migliori e più rilevanti sono proprio quando beccate le frasi in mezzo a un testo in tema, che difficilmente non saranno articolate visto che nessuno parla come un cazzo di robot.

Un esempio stupido ... è più probabile ottenere dei risultati rilevanti cercando con varie frasi articolate tipo "marmellata di fragole" piuttosto che scrivere in stile telegramma "marmellata fragole".

E qui non vi metto neanche la pubblicità in mezzo al testo ... tanto nei 40 euro di tenuta server annui non ci rientro comunque.

Buon proseguimento e buone ricerche.

37. Overload dell'interfaccia di Iterazione

Illustrerò in questo capitolo l'ultimo overload che vedrà come protagonista l'interfaccia nativa Iterator.

Implementando questa interfaccia nelle nostre classi, avremo modo di fornire gli strumenti a PHP per gestire tali oggetti nei cicli di iterazione.

L'overload di questa interfaccia abbinata ad ArrayAccess vi consentirà di costruire degli strumenti veloci e flessibili per interagire più rapidamente con le vostre classi.

Gli indici per effettuare le iterazioni sugli oggetti dovranno essere attributi interni della classe.

Vediamo come è costituita l'interfaccia Iterator :

interface Iterator

  • void rewind()
  • mixed current()
  • mixed key()
  • void next()
  • bool valid()
  • rewind() - Azzera l'indice del ciclo facendolo tornare all'inizio
  • current() - Ritorna il valore della posizione corrente
  • key() - Restituisce l'indice / chiave della posizione corrente
  • next() - Aumenta l'indice per puntare all'elemento successivo
  • valid() - Ritorna true o false per controllare se l'array è finito (viene interrogato prima di una chiamata a current() o key())

Esempio overload Iterator

Per creare un esempio più semplice possibile non ho volutamente utilizzato l'ereditarietà, ma ho creato due semplici classi base.

La prima classe è Unità e rappresenta un unità presente in un magazzino, con il nome, prezzo e codice.

La seconda e ultima classe (Magazzino) è un semplice contenitore di Unità, che memorizzerà utilizzando un array classico monodimensionale, dove l'elemento singolo non sarà un semplice valore ma l'oggetto più complesso Unità.

<?php

	class Unita
	{
		public $nome;
		public $prezzo;
		public $codice;

		public function Unita($n, $p, $c)
		{
			$this->nome = $n;
			$this->prezzo = $p;
			$this->codice = $c;
		}
	}

	class Magazzino implements Iterator
	{
		private $pezzi;
		private $current; // Indice interno per gestire l'iterazione

		public function Magazzino()
		{
			$this->pezzi = array();
		}

		/* Inizio Interfaccia Iterator */
		function rewind()
		{
			$this->current = 0;
		}

		function current()
		{
			return $this->pezzi[$this->current];
		}

		function key()
		{
			return $this->current;
		}

		function next()
		{
			return $this->current++;
		}

		function valid()
		{
			return isset($this->pezzi[$this->current]);
		}
		/* Fine Interfaccia Iterator */

		public function aggiungi(Unita $unita)
		{
			array_push($this->pezzi, $unita);
		}
	}

	$store = new Magazzino();

	$store->aggiungi(new Unita("Le avventure dello zio Pasquale", 15.5, "A096H"));
	$store->aggiungi(new Unita("I delitti di Grottaferrata", 12.0, "B076X"));
	$store->aggiungi(new Unita("Quando dico Basta!!", 23.5, "KJ87F2"));

	foreach ($store as $unita)
	{
		echo "Nome = $unita->nome<br />\n";
		echo "Prezzo = $unita->prezzo<br />\n";
		echo "Codice = $unita->codice<br /><br />\n\n";
	}

?>

Il codice produce il seguente risultato :

Nome = Le avventure dello zio Pasquale
Prezzo = 15.5
Codice = A096H

Nome = I delitti di Grottaferrata
Prezzo = 12
Codice = B076X

Nome = Quando dico Basta!!
Prezzo = 23.5
Codice = KJ87F2


36. Overload dell'operatore di accesso degli Array

Un'altra caratteristica che rende molto versatile PHP 5 è l'overloading dell'operatore di accesso degli Array : []

Per effettuare questo overload è necessario far sì che le nostre classi implementino l'interfaccia nativa ArrayAccess.
Quest'ultima fornisce i metodi astratti che ci permetteranno di utilizzare le istanze delle nostre classi come se fossero degli array omettendo quindi il classico operatore di selezione -> con i vantaggi che ne derivano dalla gestione di un array :

<?php

	class Oggetto implements ArrayAccess
	{
		public $attributo1 = "ciao";
		public $attributo2 = "pippo";

		/* Ridefinizione dei metodi di ArrayAccess
		
		...
		
		*/
	}

	$obj = new Oggetto();

	echo $obj["attributo1"] . " " . $obj["attributo2"]; // Stampa "ciao pippo"

?>

Vediamo cosa contiene l'interfaccia ArrayAccess prima di illustrare l'overload vero e proprio e le sue potenzialità.

interface ArrayAccess

  • bool offsetExists ($index)
  • mixed offsetGet ($index)
  • void offsetSet ($index, $new_value)
  • void offsetUnset ($index)

offsetExists viene richiamato per verificare se esiste l'indice specificato nel nostro pseudo-array.

offsetGet è invocato quando si tenta di leggere un valore all'indice specificato.

offsetSet è chiamato quando si tenta di scrivere un valore all'indice specificato.

offsetUnset è invocato quando si tenta di cancellare un valore all'indice specificato.


Esempio ArrayAccess

Di seguito un esempio dove mostrerò come unire le potenzialità di un oggetto al pratico sistema di gestione degli array.

Il codice nell'esempio ovviamente è solo parzialmente scritto in quanto serve solo a far capire il funzionamento di questo overload.

Ho creato una classe Impiegati che rappresenta una lista di impiegati e i lori stipendi gestita come se fosse un array, ma essendo anche un oggetto potrà vantare altri metodi per avviare operazioni più complesse come la stampa su schermo dell'intera lista degli impiegati o il salvataggio di quest'ultima su un file esterno.

<?php

	class Impiegati implements ArrayAccess
	{
		private $lista;

		public function Impiegati()
		{
			$this->lista = array();
		}

		private function convertiChiave($chiave)
		{ return ucwords(strtolower($chiave)); }

		function offsetExists($nome)
		{
			/* Converto la chiave dell'array assicurandomi che sia sempre minuscola
			con la sola iniziale maiuscola in modo da rendere gli accessi case-insensitive

			NOTA : le chiavi degli array nel linguaggio nativo sono invece case-sensitive */

			$nome = $this->convertiChiave($nome);

			foreach ($this->lista as $chiave => $valore)
				if ($chiave == $nome)
					return true;

			return false;
		}

		function offsetGet($nome)
		{
			$nome = $this->convertiChiave($nome);

			if ($this->offsetExists($nome))
				return $this->lista[$nome];
			else
				return NULL;
		}

		function offsetSet($nome, $valore)
		{
			$nome = $this->convertiChiave($nome);
			$this->lista[$nome] = $valore;
		}

		function offsetUnset($nome)
		{
			$nome = $this->convertiChiave($nome);

			if ($this->offsetExists($nome))
				$this->lista[$nome] = "Licenziato";
		}

		public function mostraStipendi()
		{
			foreach ($this->lista as $chiave => $valore)
			{
				if ($valore == "Licenziato")
				{ echo "$chiave è stato $valore<br />\n"; }
				else
				{ echo "$chiave ha uno stipendio di $valore euro<br />\n"; }
			}
		}

		public function salvaSuFile($nomefile)
		{
		    /* Codice per salvare la lista degli impiegati su file

		    ...

		    */
		}
	}

	$impiegati = new Impiegati();

	$impiegati["Mario"] = 1250;
	$impiegati["Luca"] = 2300;
	$impiegati["Giovanni"] = 1570;
	$impiegati["MARIO"] = 3740; // Sovrascrive "Mario" perchè converto sempre la chiave : ucwords(strtolower($chiave))

	// Licenzia Luca
	unset($impiegati["Luca"]);

	$impiegati->mostraStipendi();

?>

L'esempio produce il seguente risultato :

Mario ha uno stipendio di 3740 euro
Luca è stato Licenziato
Giovanni ha uno stipendio di 1570 euro


Condividi contenuti