Serialización de objetos en PHP5
August 4th, 2008La serialización es una de esas cuestiones que suelen pasar desapercibidas en PHP pero que permiten, entre otras cosas, incrementar significativamente el rendimiento de las aplicaciones. Nos permite crear representaciones de texto de cualquier dato de PHP, como arrays y objetos, lo cual abre las puertas a que esta información se almacene por ejemplo en caché para evitar repetir cálculos complejos en peticiones siguientes, por ejemplo.
Hace ya más de dos años hablé sobre ella en un extenso artículo dedicado a la programación orientada a objetos con PHP4. En esta ocasión voy a aportar un poco de luz sobre alguna característica peculiar de la serialización de objetos en PHP5.
El funcionamiento de la serialización en PHP5 es muy parecido a como funciona en PHP4. Se basa en el uso de las funciones serialize y unserialize, que permiten serializar y deserializar respectivamente. La primera de ellas acepta como parámetro cualquier elemento que queramos serializar, como un array o un objeto y obtiene una representación equivalente en forma de cadena de texto. Mediante la segunda de las funciones es posible recuperar la estructura de datos original a partir de una cadena de texto previamente serializada.
En el caso de los objetos muchas veces es necesario también el uso de dos métodos “mágicos” como __sleep y __wakeup. Estos métodos permiten preparar el objeto para ser serializado y reconstruir aquello que sea necesario tras una deserialización. Además mediante __sleep se puede indicar qué datos del objeto son serializables y cuáles no. Imaginemos que tenemos un objeto con multitud de campos de los cuales es únicamente necesario serializar un subconjunto de ellos. Para ello deberíamos implementar el método __sleep haciendo que devuelva un array con los nombres de los atributos del objeto que hay que serializar.
Y es aquí donde radica la principal diferencia con PHP4, ya que ahora estos atributos tienen una visibilidad (pública, protegida o privada) que es necesario tener en cuenta. En el caso de que queramos serializar atributos públicos no hay problema y será suficiente con devolver su nombre en un array, tal como se muestra en el siguiente ejemplo.
class Ejemplo {
public $atributo;
public function __sleep() {
return array("atributo");
}
}
Sin embargo en el caso de querer serializar atributos protegidos o privados es necesario indicar su nombre de forma especial, antecediendo una cadena de texto que indique la visibilidad del atributo. Para ello los diseñadores de PHP5 han tenido la “brillante idea”, y digo brillante porque desde luego es una aberración desde el punto de vista de la programación orientada a objetos, de anteponer una cadena de texto especial al nombre de cada atributo. Así por ejemplo “\0*\0″ se antepondría a los atributos protegidos y en el caso de los atributos privados se sustituiría el “*” por el nombre de la clase en el que esté definido.
class Ejemplo {
protected $bar1;
private $bar2;
public function __sleep() {
return array("\0*\0bar1","\0Ejemplo\0bar2");
}
}
Sin embargo, ¿qué sucede si desde una clase hija de una jerarquía queremos serializar un objeto que tiene atributos privados heredados de la clase superior? Pues he aquí la aberración, que este método nos permite indicar los atributos anteponiendo el nombre de la clase en la que están definidos, saltándonos de un plumazo toda la ocultación de información, uno de los pilares del paradigma de programación orientada a objetos.
class Base {
private $bar1 = "privado base";
protected $bar2 = "protegido base";
public function __sleep() {
}
}
class Hija extends Base {
public $hbar1 = "público hija";
private $hbar2 = "privado hija";
public function __sleep() {
// ESTO ES UNA ABERRACIÓN!!!!!
return array("\0Base\0bar1","\0*\0bar2","hbar1","\0Hija\0hbar2");
}
}
$test = new Hija();
$s = serialize($test);
$test2 = unserialize($s);
echo $s;
print_r($test);
print_r($test2);
Para apañar esta situación existe una solución que permite evitar los problemas inherentes, aunque depende de las buenas constumbres y rigor de cada desarrollador. Lo más correcto sería disponer de un método __sleep en la clase padre que indicase los atributos a serializar y que desde la clase hija se llamase a este método mediante parent::__sleep() y el string de resultado se añadiese al de la propia clase con array_merge. Así de esta forma desde el método __sleep de cada clase de la jerarquía se indican los atributos propios de la clase (públicos, protegidos y privados) y a la hora de serializar se realiza una llamada por la jerarquía de forma ascendente, de forma que cada clase indique sus atributos y estos se vayan añadiendo a un array.
class Base {
private $bar1 = "privado base";
protected $bar2 = "protegido base";
public function __sleep() {
return array("\0Base\0bar1", "\0*\0bar2");
}
}
class Hija extends Base {
public $hbar1 = "público hija";
private $hbar2 = "privado hija";
public function __sleep() {
return array_merge(array("hbar1", "\0Hija\0hbar2"), parent::__sleep());
}
}
$test = new Hija();
$s = serialize($test);
$test2 = unserialize($s);
echo $s;
print_r($test);
print_r($test2);
delicious
menéame
fresqui

August 4th, 2008 a las 7:05 pm
No aparece correctamente el segundo ejemplo (El de los atribujtos privados).
Saludos.
August 4th, 2008 a las 7:09 pm
Por cierto, un buen artículo
August 4th, 2008 a las 7:24 pm
Vaya, pues sí que te has dado cuenta rápido, porque acabo de publicarlo. Estaba ya con ello, pero gracias de todas formas
August 5th, 2008 a las 4:38 pm
Muy buen articulo, y muy bien tocado el tema de serializacion.
Saludos.