WordPress y las bases de datos NoSQL

WordPress noSQL

Recientemente entre diversos proyectos (de los que tengo que publicar aquí partes de código interesantes) ha surgido la posibilidad de colaboración con unos amigos para un proyecto muy interesante y que ya hacía un tiempo que venía desarrollando una parte. Sin entrar en detalles y resumiendo mucho, vamos a hablar de fichas de productos, que posteriormente se podría ampliar con comparativas, rankings y muchas otras posibilidades, siempre y cuando la base de datos de características de productos esté bien pensada.

Por este motivo, la fase inicial de planteamiento es decisiva, aunque los clientes siempre quieren empezar a ver algo tangible (es normal, ver resultados y no sólo conceptos), yo prefiero centrarme en «las tripas», la parte de backend que aunque no veamos, podrá asegurar el éxito o darnos el fracaso del proyecto a posteriori.

En este caso, todo lo que mostremos al usuario final en una entrada, en un CPT (Custom Post Type) o mediante un shortcode, de momento me parece secundario, si va en forma de tabla, colores vivos, esquema… quiero estructurar bien los datos y después ya lo mostraremos como quieras, ¿en tabla?, no hay problema, ¿con iconos de fondo para cada sección?, ok ¿con letra Comic Sans?, bueno… todo tiene un límite, hasta ahí no llegan mis conocimientos, hay que saber mucho para mostrar un listado de características de un producto con Comic Sans, a no ser que anuncies que has localizado el bosón de Higgs.

Así que todo se reduce a la estructuración de los datos, pero primero debemos tener dichos datos y cuando se trata de introducir los datos de cientos de productos o miles, el entusiasmo inicial se suele tornar en desesperación, maldecir a los productos, al ordenador y de paso también al informático que ha creado mal la aplicación, porque reconozcamoslo, los programadores de backend aunque sepamos algo de UI/UX, damos por supuesto muchas cosas ¿que no sabías que los números tienes que ponerlos sin separadores de miles? pero si es evidente… ¿que falla al poner ese apostrofe?, hombre, si tenías que saber que hay que escaparlo… y así nos va.

En resumen, que si la mayoría de los datos que vamos a introducir en los productos ya están en los miles de páginas que están por la web… ¿por que no copiamos y pegamos? pues evidentemente así nos ahorraremos mucho trabajo, quedando para el usuario traducir los datos que están en inglés al español y pegarlos en el lugar adecuado… pero ¿y sí le decimos a la máquina que se ocupe de copiar y pegar? mejor aún ¿y si le decimos que copie, traduzca y pegue los datos?, y nosotros nos ocupamos de revisar que todo esté correcto y corregir los posibles fallos.

Pues con esa idea me puse en marcha, a crear un scraper para extraer de algunas webs los datos que nos interesaban, mediante un par de matrices de correlaciones traducir dichos datos al español y el resultado guardarlo en la base de datos.

Creación del scraper

La creación del scraper se basa en curl (si se acaba integrando en un plugin de WordPress usaré wp_remote_get), al que mediante un formulario se le pasa una url de la que obtener los datos, se descarga el html y se guarda el archivo en local a modo de caché, si volvemos a pedir la misma url y existe el archivo html no se descarga. Después se abre el archivo y se devuelve su estructura xml mediante DOMDocument y simplexml_import_dom .

/**
 * Converrt html file in xml.
 *
 * @param string $file Path to html file that will be converted to xml.
 */
function file2xml( $file ) {
	libxml_use_internal_errors( true );

	$html                     = file_get_contents( $file );
	$doc                      = new DOMDocument();
	$doc->strictErrorChecking = false;

	$doc->loadHTML( mb_convert_encoding( $html, 'HTML-ENTITIES', 'UTF-8' ) );

	libxml_use_internal_errors( false );

	$xml = simplexml_import_dom( $doc );

	return $xml;
}

Y después ya se trata de obtener los datos de la estructura XML resultante mediante xpath:

$specs = $xml->xpath( '//div[@class="ourClassWithSpecs"]/table/tbody/tr' );

foreach ( $specs as $node ) {
	$field = trim( $node->th );

	if ( is_object( $node->td->ul->li ) ) { // We have several li values.
		$li_val_array = array();

		foreach ( $node->td->ul->li as $li_val ) {
			$li_val_array[] = trim( $li_val );
		}

		$value = implode( ';', $li_val_array );
	} else {
		$value = trim( $node->td );
	}

	$bd_fields[] = normalice_db_field( $field );
	$bd_values[] = translate_value( $value );
}

Y ya tenemos todos los datos listos para guardar en nuestra base de datos y hacer después lo que queramos con ellos, este es un ejemplo sencillo de scraper, el de algunas páginas se complica bastante más.

Guardando en la base de datos

Combining SQL and NoSQL
Bases de datos NoSQL, ¿daba la charla alguno del equipo?

Una vez obtenidos los datos guardarlos en la base de datos es cosa de coser y cantar, siempre que tengamos la tabla adecuada, claro.

Creamos una tabla para los diferentes dispositivos, pero… según el dispositivo obtenido, puede tener 20 campos o 90… y con el paso del tiempo van aumentando dichos campos ya que se añaden nuevas funcionalidades. Tenemos varias opciones:

  • Crear la tabla con todos los posibles campos e ir añadiendo más campos según se vayan necesitando.
  • Crear un campo de texto y guardar dentro todos los datos/campos/valores en JSON.
  • Usar una base de datos NoSQL y gestionar correctamente los datos obtenidos sin limitaciones a la hora de guardar, recuperar y buscar.
  • Crear una tabla de producto con el nombre y los campos imprescindibles/principales y añadir las demás características en una tabla adicional con el id de producto, característica y valor.

La primera opción se hace inviable si no queremos acabar con una tabla con cientos de campos y muchos en blanco, una tabla intratable y tipo queso gruyer.

La segunda opción es factible a priori, ya que tenemos los arrays de datos y valores, en algunos casos con subniveles, que ya no tendremos o bien que serializar o convertir a cadenas para almacenarlas en un campo de texto. No hay ningún problema en guardar el JSON en la base de datos, recuperarlo a posteriori y presentar los datos como queramos. El problema es buscar dentro de dichos datos sin extraerlos, por ejemplo, buscar todos los modelos de antes de 2012 y de menos de 1.000€… si estos datos están en el JSON, esto se hace imposible (al menos de momento).

La tercera opción es la ideal, guardamos nuestro JSON con todas las características de cada producto en la base de datos y podemos buscar por cualquiera de los campos del JSON, es decir, tenemos una tabla con nuestros productos y sin una estructura predefinida a la que debamos amoldarnos, perfecto, ya los tenemos, ¿no?. Usamos MongoDB con sus colecciones de documentos y estupendas características… que no nos vale ya que la mayoría de hostings de WordPress no disponen de dicha base de datos, ni la podemos administrar directamente desde WordPress (a priori, que todo es posible).

Algunas pruebas realizaré con MongoDB ya que me he creado una cuenta en Atlas y levantado el primer cluster en Google Cloud Platform, por lo que si el tiempo me lo permite, en algún momento pondré por aquí mis experimentos con WordPress y MongoDB en un cluster externo.

Pero desde la version 5.7.8 de MySQL tenemos funciones de JSON, así como un tipo de datos (al igual que en Percona, en MariaDB tenemos funciones JSON, pero no campo, podemos hacer un CHECK en los INSERT y UPDATES para verificar el JSON_VALID ). Con esto podemos realizar maravillas entre SQL y NoSQL como:

SELECT
    *
FROM
    `products`.`products`
WHERE
    `category_id` = 69
AND `attributes` -> '$.ports.usb' > 0
AND `attributes` -> '$.camera.resolution' > 12;

pero… siempre tiene que haber un pero. La mayoría de los hostings no disponen de la rama 5.7.8 o superior (de MySQL, Percona o equivalente de MariaDB). Deberíamos irnos a un dedicado, un Cloud… y el hosting donde se ejecuta el proyecto en cuestión, a pesar de ser un buen hosting especializado en WordPress y con software actualizado, dispone de Percona 5.6.36

Algo que aprendí en las diversas pruebas realizadas es que el siguiente servidor que aprovisione lo haré con Percona (este virtual de Taberna WordPress tiene MariaDB), ya no por temas de rendimiento, sino de compatibilidad y algún que otro tema 😉

Y pasamos a la cuarta opción. Nada nuevo, directamente es lo que se utiliza en WordPress en cantidad de ocasiones, por ejemplo un Post donde tenemos en una tabla principal una serie de datos básicos y en una tabla alternativa genérica {prefijo}_postmeta con su post_id  con el que identificamos a que entrada pertenece, su meta_key  para identificar lo que queremos guardar (por ejemplo número de USBs) y meta_value  con el propio valor.

Esta cuarta opción será la utilizada aunque con unas tablas específicas en el plugin realizado (que se crean con dbDelta en la activación del plugin), es decir, nada nuevo, pero ahí quedan las excelentes posibilidades que nos brindaría poder utilizar JSON directamente en la base de datos (ya hemos visto que podemos usarlo en un campo TEXT pero sin las maravillosas funciones JSON de la BD).

¿Os imaginais que WordPress utilizase una tabla con  post_id  y attributes  como JSON? ¿veis las ventajas que tendría? sobre todo para plugins como WooCommerce donde podríamos tener el producto con sus campos básicos como SKU y precio y todas sus características en un JSON que podría contener arrays, objetos… multitud de posibilidades, pero de momento no lo veremos al menos por temas de compatibilidad.

¿Posibilidades en geoposicionamiento? ¿almacenamiento de datos complejos en un CMS como WordPress? la verdad es que tenemos infinidad de posibilidades, como siempre usando WordPress como base y extendiéndolo todo lo que necesitemos tanto para hacer la web de la tienda de la esquina como portales con millones de visitas diarias y complejidad inimaginable.

Lo que siempre digo y nunca cumplo: espero ir escribiendo más a menudo todos los avances y código de estos meses, pero el tiempo…

3 comentarios en «WordPress y las bases de datos NoSQL»

Deja un comentario