Serialización de objetos en PHP5
La 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); |
No aparece correctamente el segundo ejemplo (El de los atribujtos privados).
Saludos.
Por cierto, un buen artículo 😀
Vaya, pues sí que te has dado cuenta rápido, porque acabo de publicarlo. Estaba ya con ello, pero gracias de todas formas 😉
Muy buen articulo, y muy bien tocado el tema de serializacion.
Saludos.
Buen articulo, me ha aclarado varias cosas, me gustaria leer en proximos articulos ejemplos practicos sobre la manera en la que podemos aplicar la serializacion en la vida diaria.
Un artículo muy bueno, gracias por el aporte. Pero que pasaría si uno de los atributos es un objeto de una clase en vez de una string? Este es rerializado en la llamada al método?
Hola José Antonio, si uno de los atributos es un objeto sería serializado también. Al deserializar el objeto volvería a su estado. En la clase de ese objeto se podría configurar __sleep y __wakeup si hubiera que tener en cuenta algún comportamiento específico al serializar y deserializar.
Excelente artículo, y el de serialización en PHP4 igual de bueno. Un saludo!!
Creo que la selección de atributos a serializar en las clase padres no debería de existir y es una de esas cosas que permiten realizar malas practicas, pero veamos que PHP esta tratando de mejorar en este paradigma, en teoría la solución aunque depende de las buenas practicas es el camino correcto, ya que en teoría podrías tener o heredar de una clase donde solo a ti te interesa o deberías de saber solo lo que necesitas y abstraerte de lo demás, ejemplo en Java puedes exteder un Class que venga compilado en un Jar, obvio no sabes todo lo que contiene la clase y ni lo requieres, en el caso de PHP podemos saber lo que contiene por que la class esta en condigo fuente, creo que en el casos de «la serializacion selectiva en una clase padre» es un error desde la concepción de la idea del desarrollador y de PHP por permitirlo, no deverias de tener el control sobre que serializar el Padre de tu clases, pero pues son unas de esas rarezas que se nos ocurren y el lenguaje nos deja, creo que esto se resumen a que es una mala practica como tantas, y que cada clase es responsable de limitar como sera serializada
Buenas tardes,
referente a la serialización de objectos PHP (objectos que contienen otros objetos), he utilizado el PEAR XML_Serializer con la limitación de serializar objectos fuera de su contexto, ya que son objectos que se passan por valor entre diferentes clases php.
Es mas completo la utilización de el serializer no PEAR? Hay otras alternativas?
Lo necesito para el parseo XSLT para realizar la capa presentacion.
Gracias!
Un apunte,
como serialización me refiero a obtener un XML.
Gracias!