venerdì 27 dicembre 2013

La OOP in PHP spiegata in un esempio - Parte 17 - Side Effect da Serializzazione di un Oggetto (deep clone alternativa)


Abbiamo visto che per mezzo della funzione serialize() si ha la possibilità di trasformare un oggetto in una stringa di testo. Tale stringa è utilizzabile con la funzione unserialize() per ottenere nuovamente l'oggetto.

Tale operazione però può avere degli effetti di cui tenere conto.

In fase di serializzazione lo stato dell'oggetto e i nomi delle classi necessarie sono trasformate in una stringa. In fase di ricostruzione è allocata della memoria per contenere l'oggetto deserializzato e, se delle sue proprietà sono degli oggetti, ulteriore memoria è allocata per contenere gli oggetti delle proprietà.


Conseguenza immediata è che serializzando e deserializzando un oggetto abbiamo di fatto clonato l'oggetto stesso, poiché nuova memoria è allocata per l'oggetto deserializzato.

Detto questo però, che tipo di clonazione è, ossia siamo in presenza di una shallow clone o di una deep clone?

Come spiegato poco prima, anche per gli oggetti utilizzati in qualità di proprietà di un oggetto è allocata la memoria e ricreato l'oggetto. Quindi siamo in presenza di una deep clone. Di norma, e credo anche per PHP, è sconsigliato utilizzare la serializzazione/deserializzazione per ottenere una deep clone, poiché richiede maggiori risorse rispetto all'utilizzo del costrutto clone e del suo collaboratore il metodo __clone().

Uno script di test per mostrare quanto detto, sebbene ci si scosti dall'esempio trattato fino ad ora è il seguente:

<?php
session_start();
if (isset($_POST['CS']))
    $_SESSION = [];

//Classi con cui effettuare il test
class Nome {
    private $nome;
    public function __construct($nome) {$this->setNome($nome);}
    public function setNome($nome) {$this->nome = $nome;}
    public function getNome() {return $this->nome;}
}

class Cognome {
    private $cognome;
    public function __construct($cognome) {$this->setCognome($cognome);}
    public function setCognome($cognome) {$this->cognome = $cognome;}
    public function getCognome() {return $this->cognome;}  
}

class NomeECognome {
    private $nome;
    private $cognome;
    public function __construct(\Nome $nome, \Cognome $cognome) {
        $this->setCognome($cognome);
        $this->setNome($nome);
    }
    public function setCognome(\Cognome $cognome) {$this->cognome = $cognome;}
    public function setNome(\Nome $nome) {$this->nome = $nome;}
    public function getCognome() {return $this->cognome;}
    public function getNome() {return $this->nome;}
}

//Creo o recupero gli oggetti dalla sessione
if (isset($_SESSION['ciro'])) {
    echo '<p>Desrializzo gli oggetti Ciro, Ciro2 e Roberto dalla sessione</p>';
    $ciro = unserialize($_SESSION['ciro']);
    $roberto = unserialize($_SESSION['roberto']);
    $ciro2 = unserialize($_SESSION['ciro2']);
} else {
    echo '<h2>Creo gli oggetti</h2><p>Creo gli oggetti Ciro, Ciro2 che uitilzza lo stesso oggetto di Ciro e Roberto che condivide l\'oggetto cognome (proprietà) con Ciro</p>';
    $ciro = new NomeECognome(new Nome('Ciro'), $c = new Cognome('Pellegrino'));
    $roberto = new NomeECognome(new Nome('Roberto'), $c);
    $ciro2 = $ciro;
    echo '<h3>Verifica post creazione</h3>';
    echo 'Ciro e Ciro2 sono lo stesso oggetto?';
    var_dump($ciro === $ciro2);
    echo 'Ciro e Roberto condividono lo stesso oggetto cognome?';
    var_dump($ciro->getCognome() === $roberto->getCognome());
}

//Salvo in sessione per usi futuri
$_SESSION['ciro'] = serialize($ciro);
$_SESSION['roberto'] = serialize($roberto);
$_SESSION['ciro2'] = serialize($ciro2);
?>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Forme Geometriche</title>
    </head>
    <body>
        <?php
        echo '<h2>Test sugli oggetti creati/recuperati</h2>';
        echo 'Il cognome per Ciro e Roberto è lo stesso oggetto? ';
        var_dump($ciro->getCognome() === $roberto->getCognome());

        echo 'Ciro e Ciro2 sono lo stesso oggetto?';
        var_dump($ciro === $ciro2);    

        echo 'Assegnando ad una variabile la deserializzazzione della serializzazione di un oggetto, la varaibile fa riferimento allo stesso oggetto?';
        $cognome2 = unserialize(serialize($ciro->getCognome()));      
        var_dump($cognome2===$ciro->getCognome());
           
        ?>
        <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
            <input type="submit" name="CS" value="Cancella Sessione" />
        </form>
        <a href="<?php echo $_SERVER['PHP_SELF']; ?>" >Ricarica la Pagina</a>
    </body>
</html>

Questo script definisce tre classi da utilizzare nel test, quindi crea o recupera dalla variabile $_SESSION gli oggetti ed esegue le sue prove.

L'output prodotto dalla sua prima esecuzione o cliccando su "Cancella Sessione" è il seguente.

Output alla prima esecuzione


Come si può osservare dai test alla sezione "Test sugli oggetti creati/recuperati" esistono delle dipendenze fra gli oggetti ossia Ciro e Ciro2 sono lo stesso oggetto, mentre il cognome per Ciro e Roberto è lo stesso oggetto. Facendo clic su ricarica, in modo da lavorare sugli oggetti preservati in sessione senza crearne di nuovi otteniamo:

Output dopo il clic sul link "Ricarica la Pagina"
Ricaricata la pagina, ossia memorizzati gli oggetti in $_SESSION con la serialize() e recuperati da $_SESSION con unserialize(), abbiamo che Ciro e Roberto non fanno più riferimento alla stessa locazione di memoria e che anche nel caso di proprietà interne all'oggetto, oggetti esse stesse, abbiamo una disgiunzione. Siamo quindi in presenza di una deep clone.

Al di là della incidentale deep clone, occprre tenere presente che gli oggetti recuperati tramite unserialize perdono ogni dipendenza fra loro al fine di evitare comportamenti inaspettati da parte della propria applicazione web.

Altra considerazione da fare nel momento in cui si serializza un oggetto è che PHP non è in grado di ricostruire eventuali collegamenti a risorse come ad esempio un link di connessione al DB. Tali elementi vanno rispristinati manualmente.