Fino a PHP 4 i passaggi dei parametri alle funzioni avveniva solo per valore. Per tipi di dati primitivi, ovvero interi, float (o anche stringhe), ciò funziona bene. Ma per tutti gli altri oggetti ciò porta a un sensibile spreco di memoria e tempo di calcolo. Nel passaggio per valore, o per copia, che dir si voglia, se si passa un oggetto $obj ad una funzione, viene creato un nuovo oggetto temporaneo, che è appunto la copia dell’oggetto passato, ed è su questo che la funzione lavora. Quindi, se l’oggetto è grosso, la creazione di una copia impiega molto tempo. Inoltre in memoria si hanno due oggetti uguali, e questo è uno spreco.
Da PHP 5 gli oggetti sono invece passati solo per reference (riferimento). Il passaggio per riferimento non effettua una copia dell’oggetto da passare, ma passa alla funzione una sorta di indirizzo dell’oggetto in memoria. La funzione può quindi lavorare direttamente sull’oggetto originale. Ovviamente si ha così un miglioramento di prestazioni. Il prossimo esempio dovrebbe rendere le idee più chiare.
Supponiamo di avere una classe che rappresenta un numero intero.
<?php
// Definizione della classe Intero class Intero {
public $valore; // valore dell'intero
// Costruttore con il valore dato
public function __construct($valore){
$this->valore = $valore;
}
}
?>
Ora svolgo alcune prove per mostrare quali differenze ci sono tra PHP 4 e PHP 5 nella gestione del passaggio degli oggetti della classe Intero ad una funzione.
<?php
// Definisco una funzione che aggiunge 10 ad un numero (tipo primitivo)
function incrementaNum($num){
$num += 10;
return $num;
}
// Definisco una funzione che aggiunge 10 al valore di un oggetto Intero
function incrementaInt($intero){
$intero->valore += 10;
return $intero;
}
// Creo un intero con valore 3
$intero = new Intero(3);
// Creo un numero con valore 3
$num = 3;
// Incremento $num
incrementaNum($num);
echo $num; // Sia PHP 4 che PHP 5 stampano 3
// Incremento $intero
incrementaInt($intero);
echo $intero->valore; // PHP 4 stampa 3, PHP 5 stampa 13
?>
Nel primo caso sia PHP 4 che PHP 5 stampano 3 poichè $num è un tipo primitivo. I tipi primitivi vengono passati per valore, a meno di indicazioni esplicite, sia in PHP 4 che in PHP 5. Quindi il numero incrementato non è $num, ma una sua copia. $num rimane quindi inalterato. Per vedere l’effetto dell’incremento sarebbe stato necessario fare:
<?php
// ...
$num = 3;
$num = incrementaNum($num);
echo $num; // Sia PHP 4 che PHP 5 stampano 13
// ...
?>
Nel secondo caso invece $intero è un oggetto. In PHP 4 gli oggetti sono passati per valore, come tutte le altre variabili, e quindi si ottiene lo stesso effetto della variabile $num. In PHP 5 invece viene passato il riferimento all’oggetto, e quindi la funzione lavora sull’oggetto stesso, non su una copia. Quindi incrementa il valore dell’oggetto reale, ed è per questo motivo che stampa 13.
Reference per i valori di ritorno
Tutto questo discorso vale anche per i valori di ritorno. Se la variabile da ritornare è un tipo primitivo sia PHP 4 che PHP 5 effettuano una copia della variabile da ritornare, e non ritornano quindi la variabile su cui la funzione ha realmente agito, ma una sua copia. Ancora, ciò funziona bene solo per i tipi primitivi. Con gli oggetti lo spreco è enorme. Pensate ad una funzione come quella definita nell’esempio precedente: l’oggetto gli viene passato tramite una copia, la funzione lo elabora, e lo ritorna effettuando un’altra copia. In pratica nello stesso momento l’oggetto iniziale è stato duplicato due volte. In PHP 5 quindi si è scelto di fare ritornare gli oggetti per reference, migliorando le performance.
Passaggio esplicito per riferimento
Se volessimo passare per riferimento anche variabili di tipi primitivi basterebbe aggiungere, all’interno della dichiarazione della funzione, il carattere & prima del nome del parametro che vogliamo passare per reference. Ecco l’esempio.
<?php
// Passaggio per riferimento
function incrementaNum(&$num){
$num += 10;
return $num;
}
$a = 3;
incrementaNum($a);
echo $a; // Stampa 13 sia in PHP 4 che in PHP 5;
?>
Se si vuole forzare anche il ritorno di tipi primitivi per riferimento basta aggiungere & prima del nome della funzione. Ecco quindi come si modifica la funzione appena definita:
<?php
// Passaggio per riferimento
function &incrementaNum(&$num){
$num += 10;
return $num;
}
?>
Tutto ciò non crea problemi nel caso si passino alla funzione degli oggetti, in quanto comunque essi sarebbero passati per reference. In questo caso si rende solamente più esplicito il meccanismo.
Assegnamento, alias e clonazione
In PHP 4 un assegnamento tra due variabili, siano esse tipi primitivi o oggetti, crea una copia dell’oggetto assegnato. In PHP 5, in base sempre alla stessa logica esposta fin qui, nel caso di oggetti la copia è solamente del riferimento, e non dell’oggetto completo.
<?php
// Assegnamento tra tipi primitivi
$int1 = 3;
$int2 = $int1;
// Assegnamento tra oggetti
$int1 = new Intero(3);
$int2 = $int1;
?>
La situazione dopo il primo assegnamento è che, dato che $int1 è un tipo primitivo, sia in PHP 4 che in PHP5 $int1 è stato copiato in $int2. Dopo il secondo assegnamento invece la situazione è diversa. In PHP 4 $int1 è sempre copiato in $int2, e quindi $int1 e $int2 risultano due oggetti fisicamente diversi. In PHP 5 invece la copia coinvelge solo il riferimento all’oggetto. Quindi lo stesso oggetto è indirizzato da due variabili, $int1 e $int2. $int2 è in pratica un alias di $int1.
Se si vuole forzare PHP 5 a copiare $int1 in $int2, ovvero si vuole un comportamento identico a quello di PHP 4, allora bisogna utilizzare la funzione clone() sull’oggetto $int1. Essa clona l’oggetto $int1 restituendone quindi una copia.
<?php
// Assegnamento con clonazione
$int1 = new Intero(3);
$int2 = clone $int1;
?>
In questo modo $int2 è una copia di $int1: $int1 e $int2 risultano quindi due oggetti fisicamente diversi.