Más con Symfony

8.2. Utilizando la cache de resultados de Doctrine

En los sitios web con mucho tráfico es necesario guardar la información en caches para aliviar algunos recursos de la CPU. En la última versión de doctrine 1.2 se han añadido muchas mejoras a la cache de resultados para tener un mejor control sobre el borrado de las entradas de la cache. Antes no se podía especificar la clave asociada con cada entrada de la cache, por lo que no era posible identificar correctamente la entrada que se quería borrar.

En esta sección se muestra un ejemplo sencillo de cómo utilizar la cache de resultados para guardar en ella todas las consultas relacionadas con los usuarios, así como el uso de eventos para borrar todas las entradas cuya información haya sido modificada.

8.2.1. El esquema

El siguiente esquema es el que se va a utilizar en este ejemplo:

# config/doctrine/schema.yml
User:
  columns:
    username:
      type: string(255)
      notnull: true
      unique: true
    password:
      type: string(255)
      notnull: true

A continuación se crean todas las clases con el siguiente comando:

$ php symfony doctrine:build --all

Después de ejecutarla, se habrá generado la siguiente clase llamada User:

// lib/model/doctrine/User.class.php
/**
 * User
 *
 * This class has been auto-generated by the Doctrine ORM Framework
 *
 * @package    ##PACKAGE##
 * @subpackage ##SUBPACKAGE##
 * @author     ##NAME## <##EMAIL##>
 * @version    SVN: $Id: Builder.php 6508 2009-10-14 06:28:49Z jwage $
 */
class User extends BaseUser
{
}

Más adelante se añadirá el código correspondiente en esta clase, así que no la pierdas de vista.

8.2.2. Configurando la cache de resultados

Antes de utilizar la cache de resultados es necesario configurar el driver de la cache que utilizarán las consultas. Esta configuración se realiza mediante el atributo ATTR_RESULT_CACHE. En este ejemplo se hace uso del driver APC porque es la mejor elección para los entornos de producción. Si no dispones de APC, puedes utilizar los drivers Doctrine_Cache_Db o Doctrine_Cache_Array para hacer las pruebas.

Este atributo se puede definir en la clase ProjectConfiguration, añadiendo un método llamado configureDoctrine():

// config/ProjectConfiguration.class.php
 
// ...
class ProjectConfiguration extends sfProjectConfiguration
{
  // ...
 
  public function configureDoctrine(Doctrine_Manager $manager)
  {
    $manager->setAttribute(Doctrine_Core::ATTR_RESULT_CACHE, new Doctrine_Cache_Apc());
  }
}

Una vez configurado el driver de la cache, ya se puede hacer uso de este driver para almacenar en la cache el resultado de las búsquedas.

8.2.3. Consultas de prueba

Imagina que tu aplicación tiene varias consultas relacionadas con los usuarios y que quieres borrarlas de la cache cada vez que se modifica alguna información del usuario.

La siguiente consulta se puede utilizar para mostrar una lista completa de todos los usuarios ordenados alfabéticamente:

$q = Doctrine_Core::getTable('User')
    ->createQuery('u')
    ->orderBy('u.username ASC');

Para guardar el resultado de esa consulta en la cache, se utiliza el método useResultCache():

$q->useResultCache(true, 3600, 'users_index');

Nota El tercer argumento del método es muy importante, ya que es la clave con la que se asociarán los resultados en el driver de la cache. De esta forma es posible identificar fácilmente a esa consulta para borrarla más adelante.

Cuando se ejecuta el código anterior, se realiza la consulta a la base de datos y los resultados se guardan en el driver de la cache bajo la clave users_index. Cuando se vuelve a ejecutar el código anterior, los resultados se obtienen directamente de la cache en vez de realizar la consulta en la base de datos:

$usuarios = $q->execute();

Nota La cache no sólo ahorra recursos en el servidor de base de datos, sino que también evita todo el procesamiento de los registros, llamado hidratación. Doctrine guarda en la cache los registros ya procesados, por lo que también se liberan recursos del servidor web.

Si ahora se busca en el driver de la cache, se obtiene una entrada llamada users_index:

if ($cacheDriver->contains('users_index'))
{
  echo 'existe la cache';
}
else
{
  echo 'no existe la cache';
}

8.2.4. Borrando la cache

Ahora que la consulta ya se ha guardado en la cache, el siguiente paso consiste en aprender a borrar esa cache. El borrado se puede realizar manualmente con la API del driver de la cache o se pueden utilizar los eventos para borrar la cache automáticamente cuando se inserta o modifica un usuario.

8.2.4.1. La API del driver de la cache

Antes de utilizarla en un evento, se va a mostrar el uso manual de la API del driver de la cache.

Nota La instancia del driver de la cache se puede obtener mediante la instancia de la clase Doctrine_Manager.

$cacheDriver = $manager->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE);

Si no está definida la variable $manager, puedes obtener la instancia correspondiente con el siguiente código.

$manager = Doctrine_Manager::getInstance();

Ahora ya se puede hacer uso de la API para borrar las entradas de la cache:

$cacheDriver->delete('users_index');

Seguramente la cache contendrá más de una consulta relacionada con los usuarios y todas ellas harán uso del mismo prefijo users_ así que el método delete() no es muy útil en este caso. En su lugar se puede utilizar el método deleteByPrefix() para borrar la cache de todas las consultas que contengan el prefijo indicado:

$cacheDriver->deleteByPrefix('users_');

Si el método deleteByPrefix() no es suficiente, existen otros métodos muy útiles para borrar entradas de la cache:

  • deleteBySuffix($sufijo): borra las entradas de la cache que contengan el sufijo indicado.
  • deleteByRegex($regex): borra las entradas de la cache cuya clave cumpla con la expresión regular indicada.
  • deleteAll(): borra todas las entradas de la cache.

8.2.5. Borrando con eventos

La forma ideal de borrar la cache consiste en que se borre automáticamente cada vez que se modifica algún dato del usuario. Para ello, sólo es necesario configurar un evento en el método postSave() de la clase del modelo User.

¿Recuerdas la clase User creada anteriormente? Abre la clase con tu editor favorito y añade el código del siguiente método postSave():

// lib/model/doctrine/User.class.php
 
class User extends BaseUser
{
  // ...
 
  public function postSave($event)
  {
    $cacheDriver = $this->getTable()->getAttribute(Doctrine_Core::ATTR_RESULT_CACHE);
    $cacheDriver->deleteByPrefix('users_');
  }
}

Ahora, cada vez que se actualiza un usuario y cada vez que se inserta un nuevo usuario, se borran de la cache todas las consultas relacionadas con los usuarios:

$user = new User();
$user->username = 'jwage';
$user->password = 'changeme';
$user->save();

Después de ejecutar el código anterior, la próxima vez que se realicen las consultas de los usuarios no existirá una cache con los resultados, por lo que se volverán a realizar las consultas en la base de datos. En las siguientes consultas, volverán a utilizarse las entradas guardadas en la cache.

Aunque el ejemplo mostrado es muy sencillo, es útil para hacerse una idea de cómo se puede utilizar esta característica de Doctrine para tener un control muy preciso de la forma en la que se guardan las consultas en la cache.