Uso avanzado de Doctrine
En esta sección se explica cómo crear un nuevo comportamiento utilizando Doctrine 1.2. El ejemplo empleado permitirá sostener una cache del númerode relaciones de un registro para no tener que hacer esa consulta todo el rato.
La funcionalidad es realmente simple: en todas y cada una de las relaciones en las que quierascontrolar su número, el comportamiento añade una columna a su modelo para almacenarun contador.
Inicialmente se va a emplear el siguiente esquema. Más adelante se modificapara añadir la definición
actAsdel comportamiento que se va a crear:
Seguidamente se edifican todas y cada una de las clases del esquema:
En primer sitio se crea la clase básica de tipo
Doctrine_Templateque serála responsable de añadir las columnas al modelo que guardará los contadores.
Añade la siguiente clase en cualquier directorio
lib/del proyecto a fin de que symfony pueda cargarla de forma automática:
A continuación se modifica el modelo
Postpara añadir el comportamiento
CountCachemediante
actAs:
Ahora que el modelo
Posthace empleo del comportamiento
CountCache, sufuncionamiento es el siguiente: cuando se instancia la información de mapeo deun modelo, se invocan los métodos
setTableDefinition()y
setUp()de todossus comportamientos asociados. Esto es exactamente lo mismo que sucede con la clase
BasePosten
lib/model/doctrine/base/BasePost.class.php. Esta característicapermite añadir elementos de todo tipo a un modelo, como columnas, relaciones,eventos, etc.
Ahora que está más claro su funcionamiento interno, se añade toda la lógicainterna del comportamiento
CountCache:
El código superior añade columnas para sostener los contadores de los modelosrelacionados. Por lo tanto, en un caso así se añade el comportamiento en el modelo
Postpara su relación
Thread. De esta manera, el número de posts de cualquier
Threadse guarda en una columna llamada
num_posts. A continuación, modifica el esquema YAML para definir las opciones auxiliares del comportamiento:
Ahora el modelo
Threaddispone de una columna llamada
num_postsy que guardará de forma actualizada el número de posts que tiene cada hilo de discusión.
El siguiente paso consiste en crear un
event listenerde registro que será elque se ocupe de sostener actualizado el contador cuando se creen nuevosregistros y cuando se borren registros de forma individual o en bloque.
Antes de continuar es preciso delimitar la clase
CountCacheListenerque extiende la clase
Doctrine_Record_Listenery que acepta un array de opcionesque sencillamente se pasan al
listenerde la plantilla:
Para mantener los contadores actualizados es necesario utilizar los siguienteseventos:
-
postInsert(): acrecienta el contador cuando se inserta un nuevo objeto
-
postDelete(): decrementa el contador cuando se borra un objeto
-
preDqlDelete(): decrementa el contador cuando se borrar varios objetos a través de un borrado DQL.
postInsert(): incrementa el contador cuando se inserta un nuevo objeto
postDelete(): decrementa el contador cuando se borra un objeto
preDqlDelete(): decrementa el contador cuando se borrar varios objetos mediante un borrado DQL.
En primer sitio se define el método
postInsert():
El código anterior incrementa en una unidad, a través de una consulta de tipo DQL UPDATE, los contadores de todas las relaciones configuradas cada vez que se inserta un nuevo objeto, como por servirnos de un ejemplo el siguiente:
El
Threadcuyo
idvalga
1incrementará en una unidad el valor de sucolumna
num_posts.
Ahora que los contadores ya se acrecientan al introducir nuevos objetos, es necesariodecrementarlos cuando se borre algún objeto. Para ello se define el siguientemétodo
postDelete():
El método
postDelete()superior es casi idéntico al método
postInsert(),siendo la única diferencia que en este caso el valor de la columna
num_postsse decrementa en una unidad. Si ahora se borra el registro creado anteriormente,el contador se actualiza correctamente:
La última parte del comportamiento debe encargarse de los borrados masivosrealizados con una consulta de tipo DQL. La solución consiste en crear un método
preDqlDelete():
El código anterior clona la consulta de tipo
DQL DELETEy la transforma enuna consulta
SELECTque deja conseguir los
IDde los registros que se marchan a borrar, de forma que se pueda actualizar adecuadamente el contador.
Ahora ya es posible manejar consultas como la próxima actualizando de formacorrecta el valor de los contadores:
El valor de los contadores se actualiza apropiadamente incluso cuando se borranvarios registros a la vez:
note
Para invocar el método
preDqlDelete()es necesario activar un atributo. La razón es que los
callbacksde DQL están desactivados por defecto porque penalizan tenuemente el rendimiento. Por ende, para usarlos es preciso activarlos:
¡Y eso es todo! El nuevo comportamiento ya está terminado. Lo último que falta por hacer es añadir algunas pruebas unitarias.
Ahora que el código ya está completado, se probará con los siguientes datosde prueba:
A continuación se ejecuta la siguiente tarea para regresar a crear todas las clases y para cargar todos los datos de prueba:
Después de regresar a crear y cargar todo, se efectúa la próxima prueba paracomprobar que los contadores se actualizan correctamente:
El valor de la columna
num_postsdel modelo
Threadvale 3. Si se borraun post mediante el próximo comando, el contador debe decrementarse:
Como se puede revisar, el registro se ha borrado y el contador se ha actualizado.
También marcha adecuadamente cuando se borran los otros 2 registros sobrantes a través de una consulta de tipo DQL.
Ahora que se han borrado todos los posts relacionados, el valor de la columna
num_postsdebería ser cero.
¡Y eso es todo! Confiamos que este artículo te haya sido útil tanto por haberaprendido a crear comportamientos como por el propio comportamiento creado.
En los sitios web con mucho tráfico es preciso guardar la información encaches para calmar ciertos 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 tenerun mejor control sobre el borrado de las entradas de la cache. Ya antes no se podíaespecificar la clave asociada con cada entrada de la cache, por lo que no eraposible identificar correctamente la entrada que se quería borrar.
En esta sección se muestra un ejemplo fácil de cómo usar la cache de resultados para guardar en ella todas y cada una de las consultas relacionadas con losusuarios, así como el uso de eventos para borrar todas las entradas cuyainformación haya sido cambiada.
El siguiente esquema es el que se marcha a utilizar en este ejemplo:
A continuación se crean todas las clases con el siguiente comando:
Después de ejecutarla, se habrá generado la próxima clase llamada
User:
Más adelante se añadirá el código pertinente en esta clase, así que no lapierdas de vista.
Antes de usar la cache de resultados es necesario configurar el driver dela cache que utilizarán las consultas. Esta configuración se realiza medianteel atributo
ATTR_RESULT_CACHE. En este caso de ejemplo se utiliza el driver APCporque es la mejor elección para los ambientes de producción. Si no dispones deAPC, puedes emplear los drivers
Doctrine_Cache_Dbo
Doctrine_Cache_Arraypara hacer las pruebas.
Este atributo se puede delimitar en la clase
ProjectConfiguration, añadiendoun método llamado
configureDoctrine():
Una vez configurado el driver de la cache, ya se puede hacer uso de este driverpara almacenar en la cache el resultado de las búsquedas.
Imagina que tu aplicación tiene múltiples consultas relacionadas con los usuariosy que quieres borrarlas de la cache cada vez que se altera alguna información del usuario.
La siguiente consulta se puede usar para enseñar una lista completa de todos los usuarios ordenados alfabéticamente:
Para guardar el resultado de esa consulta en la cache, se utiliza el método
useResultCache():
note
El tercer razonamiento 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 manera es posible identificar fácilmente a esa consulta para borrarla más adelante.
Cuando se ejecuta el código precedente, se realiza la consulta a la base de datosy 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 obtienendirectamente de la cache en lugar de efectuar la consulta en la base de datos:
note
La cache no sólo ahorra recursos en el servidor de base de datos, sino también evita todo el procesamiento de los registros, llamado
hidratación. Doctrine guarda en la cache los registros ya procesados, con lo que también se liberan recursos del servidor web.
Si ahora se busca en el driver de la cache, se consigue una entrada llamada
users_index:
Ahora que la consulta ya se ha guardado en la cache, el próximo paso consisteen aprender a borrar esa cache. El borrado se puede efectuar manualmente conla API del driver de la cache o bien se pueden emplear los eventos para borrar lacache automáticamente cuando se inserta o bien modifica un usuario.
La API del driver de la cache
Antes de usarla en un evento, se marcha a enseñar el empleo manual de la API deldriver de la cache.
tip
La instancia del driver de la cache se puede conseguir mediante la instancia de la clase
Doctrine_Manager.
Si no está definida la variable
$ manager, puedes obtener la instancia pertinente con el próximo código.
Ahora ya se puede hacer uso de la API para borrar las entradas de la cache:
Seguramente la cache contendrá más de una consulta relacionada con losusuarios y todas y cada una harán empleo del mismo prefijo
users_así que el método
delete()no es muy útil en este caso. En su sitio se puede usar el método
deleteByPrefix()para borrar la cache de todas las consultas que contengan elprefijo indicado:
Si el método
deleteByPrefix()no es suficiente, hay otros métodos muyútiles para borrar entradas de la cache:
-
deleteBySuffix( dólares americanos sufijo): borra las entradas de la cache que contengan elsufijo indicado. -
deleteByRegex( dólares americanos regex): borra las entradas de la cache cuya clavecumpla con la expresión regular indicada. -
deleteAll(): borra todas y cada una de las entradas de la cache.
deleteBySuffix( dólares americanos sufijo): borra las entradas de la cache que contengan elsufijo indicado.
deleteByRegex( dólares americanos regex): borra las entradas de la cache cuya clavecumpla con la expresión regular indicada.
deleteAll(): borra todas las entradas de la cache.
La forma ideal de borrar la cache consiste en que se borre automáticamentecada vez que se modifica algún dato del usuario. Para esto, sólo es necesarioconfigurar un acontecimiento en el método
postSave()de la clase del modelo
User.
¿Recuerdas la clase
Usercreada previamente? Abre la clase con tu editorfavorito y añade el código del siguiente método
postSave():
Ahora, toda vez que se actualiza un usuario y cada vez que se introduce un nuevousuario, se borran de la cache todas y cada una de las consultas relacionadas con los usuarios:
Después de ejecutar el código precedente, la próxima vez que se realicen las consultasde los usuarios no existirá una cache con los resultados, con lo que se volverána efectuar las consultas en la base de datos. En las próximas consultas,volverán a utilizarse las entradas guardadas en la cache.
Aunque el ejemplo mostrado es sencillísimo, es útil para hacerse una idea de cómo se puede posicionamiento en google maps ística de Doctrine para tener un controlmuy preciso de la forma en la que se guardan las consultas en la cache.
Una de las principales características de Doctrine en su habilidad paratransformar un objeto de tipo
Doctrine_Queryen resultados con diferentesestructuras. Esta tarea la efectúan los
hydratorsde Doctrine y hasta laversión 1.2 de Doctrine los programadores no podían crear sus
hydrators.Ahora que es posible hacerlo, se puede desarrollar un
hydratorpropio paracrear cualquier clase de estructura desde los resultados obtenidosmediante
Doctrine_Query.
El siguiente ejemplo muestra cómo crear un
hydratormuy fácil y fácil de comprender, mas al unísono muy útil. El funcionamiento del
hydratorconsiste enseleccionar un par de columnas y transformarlas en un array asociativo en el que la clave de cada elemento del array es el valor de la primera columna yel valor de cada elemento del array es el valor de la segunda columna.
Para efectuar las pruebas se va a emplear el próximo esquema de un modelosencillo llamado
User:
Como también son precisos algunos datos de prueba, se va a hacer uso de lossiguientes:
A continuación ejecuta la próxima labor para crear todas y cada una de las clases:
Para crear un
hydratorsólo es preciso crear una nueva clase que herede de
Doctrine_Hydrator_Abstracty que implemente un método llamado
hydrateResultSet( dólares americanos stmt).Este método recibe como argumento una instancia del
PDOStatementutilizado paraejecutar la consulta. Por lo tanto, se puede emplear este objeto para obtener losresultados de la consulta de forma directa del PDO y transformarlos en la estructuradeseada.
Se crea una nueva clase llamada
KeyValuePairHydratory se pone en el directorio
lib/para que symfony pueda cargarla automáticamente:
El código anterior por el momento sólo devuelve los datos tal como los devuelve PDO. Esto no es lo que deseamos, en tanto que queremos convertir losdatos en una estructura de tipo
clave => valor. Altera en consecuencia el método
hydrateResultSet()para llenar su funcionalidad:
¡Ha sido bastante fácil! El código del
hydratorya está terminado y haceexactamente lo que queríamos, así que vamos a probarlo.
Antes de usar el
hydratores necesario registrarlo en Doctrine para queesté libre cuando se ejecuten las consultas. Para esto, regístralo en la instancia del
Doctrine_Manageren la clase
ProjectConfiguration:
Ahora que el
hydratorya está registrado, se puede usar en cualquierinstancia de
Doctrine_Query, tal y como muestra el siguiente ejemplo:
Si se ejecuta el código precedente con los datos de prueba mostrados anteriormente,el resultado es el siguiente:
¡Y eso es todo! Bastante fácil, ¿verdad? Esperamos que te haya sido útil y quete animes a crear
hydratorsinteresantes y los compartas con el resto de la comunidad.