Como cachear render arrays en Drupal 7

Autor: Rolando Payán Mosqueda

 

Drupal 7 incluye varias características, entre ellas, los render arrays. Si no estás familiarizado con los render arrays la documentación en drupal.org o este artículo en español te explicará muy bien lo que son, por que utilizarlos y como usarlos.

Unas de las características específicas de los render arrays que capturó mi atención fue cuando por primera vez vi la propiedad #cache. Esta te permite, cachear elementos individuales de tus render arrays usando el sistema de cache de drupal, así que por ejemplo, si tienes algún código que tarde mucho en ejecutarse para mostrar algún contenido, éste puede ser almacenado en caché y posteriormente ser recuperado y ahorrar valiosos segundos o milisegundos a la hora de generar nuestra página.

Un ejemplo

Entonces, ¿cómo podemos usarlo? Aquí podemos ver un ejemplo, es parecido al que existe en render_example.module en el módulo Examples:

Note que estamos retornando un array sin renderizar. Esta es un función que se ejecutará al visitar una página, así que se espera que el “theme layer” pase este array por drupal_render() después de que otros módulos o temas tengan el chance de modificarlo. En otros momentos podríamos fácilmente invocar a drupal_render() nosotros mismos y podríamos en vez de:

return $page_array;

sustituirlo por:

return drupal_render($page_array);

Si hacemos esto, nuestro render array carece de sentido, ya que no podría ser modificado por otros módulos o temas.

El render array es muy sencillo. La propiedad #markup es el contenido del array, que será puesto en caché. La propiedad #cache le dice a Drupal como nosotros queremos que sea cacheado.

 

'keys' => array('render_example', 'cache', 'demonstration'),

La clave “key” es un array de string que unidos debe ser único, y es el identificador de nuestro contenido, este será concatenado, separado por colons y usado como el id de nuestra caché, en nuestro caso el resultado sería: render_example:cache:demonstration

 

'bin' => 'cache',

La clave “bin” es la tabla donde guardaremos nuestro contenido. Aquí usamos la tabla genérica cache, pero puedes también usar tu propia tabla personalizada. Crear una tabla personalizada es sencillo. En nuestro archivo .install (mymodule.install) adicione:

Cambie “mymodule” por el nombre de su módulo. Con este ejemplo pudiera usted configurar la clave “bin” con el valor “cache_mymodule”, ejemplo:

'bin' => 'cache_mymodule',

 

'expire' => time() + $interval,

La clave “expire” es un timestamp que le dice a Drupal cuando nuestro contenido cacheado no será válido y deberá ser reconstruido. Merece la pena aclarar que los caché que expiren serán limpiados la próxima vez que el cron sea ejecutado, por lo que se puede encontrar el caso en que esté viendo un contenido en caché y su tiempo ya ha expirado.

'granularity' => DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE,

La clave “granularity” indica si y en que situaciones este contenido debe ser cacheado. El contenido puede ser diferente dependiendo del usuario que está viéndolo, en la página que aparece, etc. Por ejemplo, no queremos que los usuarios anónimos vean contenido que se ha almacenado en caché para un administrador. Esto nos permite almacenar versiones de caché separadas por rol para evitar esa situación. Se pueden usar estos valores:

  • DRUPAL_CACHE_PER_PAGE

  • DRUPAL_CACHE_PER_ROLE

  • DRUPAL_CACHE_PER_USER

  • DRUPAL_CACHE_GLOBAL

  • DRUPAL_CACHE_CUSTOM

  • DRUPAL_NO_CACHE

Un problema

Así que parece fácil. Hemos sido capaces de añadir almacenamiento en caché en nuestro render array con sólo unas pocas líneas. Pero hay un problema. ¿Lo has visto? Tal vez sea más fácil de ver si reescribimos nuestro ejemplo.

Movimos nuestro contenido a una función aparte y adicionamos drupal_set_message(). ¿Por qué los hicimos? Si recargamos varias veces nuestra página verá el problema. El render array está cacheando nuestro contenido. Usted puede decir esto, ya que cada vez que se refresca la página la hora sigue siendo la misma que cuando se cargó por primera vez. Pero el mensaje de Drupal si muestra la hora actualizada cada vez que se recarga la página. Espera, ¿por qué este mensaje aparece en cada recarga de la página? ¿El objetivo de cachear nuestra función expensive_stuff() no era para que no se ejecutara y así ganar tiempo en la carga de nuestra página? Si la función se ejecuta en todo momento, nuestro código para cachear el contenido no tiene sentido. Para aclarar esto necesitamos un poco más de trabajo en nuestra propiedad #cache.

Este comportamiento debe ser obvio en retrospectiva. Los render arrays son simplemente arrays normales, con un formato particular y pasados a drupal_render(). drupal_render() no tiene una habilidad mágica para llegar a tiempo y evitar que nuestra función expensive_stuff() se ejecute si encuentra una versión en caché. Entonces, ¿cómo hacemos para que esto funcione?

Una solución

La solución es la propiedad #pre_render. pre_render nos permite que le pasemos un array con los nombres de las funciones que se llamarán justo antes de que el array sea renderizado. La clave aquí es que drupal_render() llama a estas funciones después de haber comprobado si existe una versión en caché del elemento. Si encuentra una versión en caché entonces esas funciones no son llamadas. Aquí está nuestro ejemplo reelaborado para utilizar #pre_render, y que #cache siga siendo útil:

Note que la propiedad #markup es ahora una cadena vacía en el render array. Técnicamente no tiene porqué estar ahí. En lugar de ello le agregamos el contenido a #markup a través de la función #pre_render que luego es responsable de llamar a nuestra función expensive_stuff(). Recargando nuestra página de ejemplo varias veces debemos darnos cuenta de que la función expensive_stuff() sólo se ejecuta la primera vez que cargamos nuestra página y luego cada vez que la caché expire.

Y eso es todo, espero que le haya sido interesante este artículo.