Uno dei pochi modi per salvare una istanza di una Classe per utilizzarla in seguito è quello di serializzarla. Se vi state chiedendo cosa diamine significhi, potrebbero essere due i casi: uno, siete dei rookie non ancora a questo livello (e non c’è niente di male), per voi ho scritto la guida PHP startup; due, non vi trovate spesso davanti a questo tipo di operazione, anche se potrebbe essere davvero utile capire come funziona.
Per farla breve, serializzare una istanza di Classe significa mettere un’intera istanza in una stringa, per poi poterla riconvertire in istanza facendo l’unserialize.
PHP mette a disposizione i metodi serialize($istanza) e unserialize($stringa) che rispettivamente convertono in stringa una istanza qualsiasi e riportano ad istanza una stringa. Vediamo un po’ di codice:
$auto = new Auto(new OlioMotore()); file_put_contents('classe.txt', serialize($car)); $stessaAuto = unserialize(file_get_contents('classe.txt')); if ($auto === $stessaAuto) { echo "Le istanze sono identiche"; }
Che abbiamo fatto? Abbiamo preso un istanza di Auto in $auto e l’abbiamo serializzata in una stringa, che abbiamo poi salvato nel file classe.txt. Al terzo rigo, abbiamo letto il contenuto del file e abbiamo passato il suo contenuto, sempre come stringa, a unserialize(), ed ecco che $stessaAuto è identica all’istanza originaria $auto.
Come vediamo, è molto utile per conservare lo stato di una data istanza in un file o in un database (o, perché no, in un database in-memory come Redis o memcached) o qualsiasi cosa accetti una stringa.
Ad esempio, salvare l’istanza dell’Utente loggato con tutte le sue proprietà in una sessione, potrebbe esserci utile per poi ripristinarla alla prossima pagina richiesta direttamente dalla sessione, senza istanziare di nuovo l’utente loggato (e tirarlo quindi nuovamente fuori dal database).
Prima di PHP 5.1
Prima di PHP 5.1, l’unico modo per gestire una serializzazione di una classe era quello di usare i metodi __sleep() e __wakeup(). Il primo doveva restituire un array di proprietà, quelle che volevamo appunto serializzare. Il secondo metodo invece doveva essere chiamato dopo che la classe fosse stata unserializzata, in modo da poter modificare l’istanza corrente. Aggiungiamo pure che la stringa generata è complicata da capire, visto che veniva fatto un encoding “strano” proprio per mantenere intatta la Classe. Facile? No.
Serializable è qui
Per fortuna, tutto questo è stato rimpiazzato da un’interfaccia chiamata, indovinate, Serializable. È provvista di due metodi, serialize() e unserialize(), che hanno sostituito la vecchia logica delle funzioni procedurali omonime, ma non hanno problemi di retrocompatibilità.
Anche se si spiegano praticamente da soli, i metodi sono:
- serialize(), che viene chiamato quando la classe viene serializzata. Il suo scopo è restituire la rappresentazione dell’instanza corrente sotto forma di stringa e non prevede parametri;
- unserialize(), che viene chiamato quando appunto tentiamo di fare l’unserialize di quella istanza. PHP controllerà l’integrità e la corrispondenza della stringa passatogli e creerà un’istanza vera e propria. Può sembrare complicato all’inizio, ma questo metodo funziona come costruttore, ciò significa che il metodo __construct() verrà bypassato. Se per noi fosse fondamentale la sua esecuzione, potremmo chiamarlo noi all’interno del metodo.
Capito? Vediamo un po’ di codice:
<?php use Serializable; class Auto implements Serializable { /** * Motore * * @var \Motore */ protected Motore $motore; /** * Colore dell'auto * * @var string */ public string $colore = 'rosso'; /** * Costruttore * * @return null */ public function __construct(Motore $motore) { $this->motore = $motore; } /** * Il motore è acceso * * @var bool */ public function motoreAcceso() : bool { return $this->motore->avviato(); } /** * Settiamo il Motore * * @param \Motore $motore * @return void */ public function setMotore(Motore $motore) { $this->motore = $motore; } /** * Resituiamo una stringa JSON encoded * * @return string */ public function serialize() { return json_encode([ 'motore' => get_class($this->motore), 'motore_on' => $this->motoreAcceso(), 'colore' => $this->colore ]); } /** * Ripristiniamo questa classe a partire da una stringa JSON * * @param string $serialized * @return void */ public function unserialize($serialized) { //La riportiamo ad array $json = json_decode($serialized); //Popoliamo gli attributi $this->setMotore($json->motore); $this->colore = $json->colore; // ... } }
Nel nostro esempio, utilizziamo JSON come formato restituito da serialize(). Abbiamo scelto di inflilarci lo stato del motore e il colore dell’auto.
Quando eseguiamo l’unserialize(), invece, prendiamo la stringa JSON in input, la convertiamo in array, e settiamo le proprietà della classe con i valori ottenuti. In due parole, decodifichiamo la stringa JSON e impostiamo la proprietà $colore con il valore trovato nella stringa e continuiamo con gli altri dati.
Vediamola in azione:
<?php $auto = new Auto(new OlioMotore()); $auto->colore = 'rosso'; // 1 // Serializziamo la classe Auto e la salviamo nel file auto.txt file_put_contents('auto.txt', serialize($auto)); // 2 // Adesso che è stata salvata, prendiamo il contenuto del file, in qualsiasi momento. // Ricordiamoci che la stringa sarà in JSON $contents = file_get_contents('auto.txt'); echo $contents; // Otterremo C:3:"Auto":63:{{"motore":"Motore\\OlioMotore","motore_on":false,"colore":"rosso"}} // 3 // Creiamo una nuova istanza basata sui dati appena presi $stessaAuto = unserialize($contents); // 4 // A questo punto controlliamo, ad esempio che il colore sia lo stesso che gli abbiamo passato in JSON echo $stessaAuto->colore; // "rosso"