Trabajando con datos serializados en WordPress

Supongamos el siguiente escenario: estás desarrollando un metabox para un tipo personalizado de contenido que representa a personas, y en el que debes agregar información de contacto como teléfono, correo electrónico, dirección, etc; la que se mostrará junto a cada persona como una ficha.

Una opción para guardar estos datos sería recurrir a alguna convención de nombres de modo que cada dato se almacena como una fila en la tabla $wpdb->postmeta; algo como _persona_telefono, _persona_email, _persona_direccion, etc. Luego, al mostrar los datos podríamos hacer una consulta como SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key LIKE '_persona%'.

Sin embargo, una mejor alternativa en este tipo de casos sería recurrir a la utilización de datos serializados, ya que nos permitirán guardar en un solo campo una estructura compleja de datos como un array (uni o multidimensional) o un objeto; lo que nos puede facilitar de forma importante varias tareas si planificamos con cuidado nuestra estructura de datos.

Por qué y cuándo preferir datos serializados en WordPress

Me atrevería a decir que en general los especialistas en bases de datos relacionales no miran con muy buenos ojos la utilización de este tipo de técnicas (ya que idealmente, cada objeto debiese estar modelado de acuerdo a un esquema que permita optimizar el rendimiento de la base de datos), sin embargo en WordPress muchas veces nos encontraremos con la necesidad de guardar un conjunto limitado (no necesariamente fijo) de datos relacionados y sin un esquema inmutable en la base de datos, ya sea como metadatos de un post (o un tipo de contenido personalizado), de un comentario o un usuario, o bien como opciones de un plugin — convenientemente, WordPress nos provee de APIs para interactuar de forma sencilla con la base de datos en estos casos, en los que la serialización de datos ya está considerada como parte del proceso de lectura/escritura desde/hacia la base de datos.

Por lo tanto, el trabajo con datos serializados nos presenta una serie de ventajas, como:

  • Mejor performance, al obtener un solo valor desde la base de datos en lugar de múltiples filas
  • Mayor orden, al trabajar con una estructura de datos más evidente con el solo hecho de obtener la información requerida
  • Mayor simplicidad al momento de guardar o recuperar datos, ya que sólo tendrás que recurrir a un campo
  • Menos posibilidades de tener conflictos con datos insertados por otros desarrolladores, ya que es más fácil utilizar un nombre único para tus claves de búsqueda

Cómo trabajar con los datos serializados

Como decía anteriormente, WordPress nos hace este trabajo muy simple, ya que las APIs de metadatos para posts, comentarios y usuarios, tanto como el API de opciones ya contemplan la posibilidad de trabajar con datos serializados.

Retomando el ejemplo del principio:

// esta es la información sanitizada a partir del envío del formulario
$person_data = array(
	'tel' => '+1 012 345 678',
	'email' => 'homero@plantanucleardespringfield.com',
	'adr' => array(
		'street-address' => 'Avenida Siempre Viva 742',
		'locality' => 'Springfield',
		'region' => '??'
	)
);

// para guardar los datos
// (el "_" antes del nombre de la opción hace que
// no se vea en la lista de campos personalizados)
update_post_meta($post->ID, '_person_data', $person_data);

// para obtener los datos
// devuelve un array similar a $person_data
get_post_meta($post->ID, '_person_data', true);

No es necesario serializar los datos antes de guardarlos ni des-serializarlos al obtenerlos, ya que WordPress internamente determinará si es necesario hacerlo (mediante las funciones maybe_serialize y maybe_unserialize, respectivamente).

Cuándo NO ocupar datos serializados

Quizás alguien a estas alturas estará pensando “¿bueno, y cuáles son las desventajas?”… muy suspicaz, ¿eh? Quizás no lo suficiente.

Al serializar el array anterior, la información que se guarda en la base de datos corresponde a:

a:3:{s:3:"tel";s:14:"+1 012 345 678";s:5:"email";s:36:"homero@plantanucleardepsringield.com";s:3:"adr";a:3:{s:14:"street-address";s:24:"Avenida Siempre Viva 742";s:8:"locality";s:11:"Springfield";s:6:"region";s:2:"??";}}

En primer lugar, habría que plantear lo obvio: cada vez que obtienes el valor necesitarás transformarlo a su representación correcta (array u objeto). A partir de ello, no tiene mucho sentido usar esta técnica cuando los datos que se almacenan no tienen mucha relación entre sí y se utilizan siempre de forma individual. Por otra parte, tampoco convendría guardar cantidades demasiado grandes de información de este modo, ya que el gasto de procesamiento y memoria sería demasiado alto.

El valor serializado que se guarda en la base de datos es simplemente una cadena de texto … el problema surge cuando queremos buscar algún dato en particular (ya que no será tan sencillo como buscar en un campo que tiene un texto simple) o bien al utilizar un dato como condición en en SELECT (por ejemplo, cuando se utiliza un campo de la tabla postmeta para obtener un conjunto de posts).

Sin embargo… aquí si conviene ser más estricto en temas de bases de datos, ya que en todos los casos contemplados (meta datos de posts, usuarios, comentarios; opciones) el campo donde se almacenan los valores (meta_value, option_value, etc) no tiene índice, por lo que las consultas que utilizan estas condiciones están fatalmente sub-optimizadas: por ejemplo, al hace una consulta como SELECT * FROM $wpdb->postmeta WHERE meta_key = '_persona_ciudad' AND meta_value = 'Springfield', la base de datos debe revisar todos los registros de la tabla en busca de coincidencias (esto no ocurre al hacer consultas como SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key = '_person_data' ya que en esas columnas existen índices que permiten que la base de datos devuelva rápidamente los valores coincidentes con los criterios indicados).

Por lo tanto, si deseas guardar muchos datos que pueden servir como criterios de búsqueda, la mejor opción en ese caso será crear una tabla especial para esa información, que te permita definir los índices adecuados según las necesidades del programa.