En esta sección se explica cómo crear un nuevo comportamiento utilizando Doctrine doce. El ejemplo usado permitirá mantener 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 marcha a emplear el siguiente esquema. Más adelante se modificapara añadir la definición
actAs
del comportamiento que se va a crear:
Seguidamente se edifican todas las clases del esquema:
En primer lugar se crea la clase básica de tipo
Doctrine_Template
que 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 altera el modelo
Post
para añadir el comportamiento
CountCache
mediante
actAs
:
Ahora que el modelo
Post
hace 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 lo mismo que sucede con la clase
BasePost
en
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. En consecuencia, en este caso se añade el comportamiento en el modelo
Post
para su relación
Thread
. De esta forma, el número de posts de cualquier
Thread
se almacena en una columna llamada
num_posts
. A continuación, modifica el esquema YAML para definir las opciones auxiliares del comportamiento:
Ahora el modelo
Thread
dispone de una columna llamada
num_posts
y 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 mantener actualizado el contador cuando se creen nuevosregistros y cuando se borren registros de forma individual o bien en bloque.
Antes de continuar es preciso delimitar la clase
CountCacheListener
que extiende la clase
Doctrine_Record_Listener
y que acepta un array de opcionesque simplemente se pasan al
listenerde la plantilla:
Para sostener los contadores actualizados es preciso emplear los siguienteseventos:
-
postInsert(): acrecienta el contador cuando se introduce 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(): acrecienta el contador cuando se introduce 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.
En primer sitio se define el método
postInsert()
:
El código anterior incrementa en una unidad, mediante una consulta de tipo DQL UPDATE, los contadores de todas y cada una de las relaciones configuradas cada vez que se introduce un nuevo objeto, como por poner un ejemplo el siguiente:
El
Thread
cuyo
id
valga
1
incrementará en una unidad el valor de sucolumna
num_posts
.
Ahora que los contadores ya se incrementan al introducir nuevos objetos, es necesariodecrementarlos cuando se borre algún objeto. Para esto se define el siguientemétodo
postDelete()
:
El método
postDelete()
superior es prácticamente idéntico al método
postInsert()
,siendo la única diferencia que en este caso el valor de la columna
num_posts
se decrementa en una unidad. Si ahora se borra el registro creado anteriormente,el contador se actualiza correctamente:
La última una 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 precedente clona la consulta de tipo
DQL DELETE
y la convierte enuna consulta
SELECT
que deja obtener los
ID
de los registros que se marchan a borrar, de forma que se pueda actualizar adecuadamente el contador.
Ahora ya es posible manejar consultas como la siguiente actualizando de formacorrecta el valor de los contadores:
El valor de los contadores se actualiza adecuadamente incluso cuando se borranvarios registros a la vez:
note
Para invocar el método
preDqlDelete()
es preciso activar un atributo. La razón es que los
callbacksde DQL están desactivados por defecto porque penalizan ligeramente el desempeño. Por tanto, para usarlos es necesario activarlos:
¡Y eso es todo! El nuevo comportamiento ya está terminado. Lo último que falta por hacer es añadir ciertas pruebas unitarias.
Ahora que el código ya está completado, se va a probar con los próximos datosde prueba:
A continuación se ejecuta la siguiente labor para volver 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 siguiente prueba paracomprobar que los contadores se actualizan correctamente:
El valor de la columna
num_posts
del modelo
Thread
vale 3. Si se borraun blog post a través de el próximo comando, el contador debe decrementarse:
Como se puede comprobar, el registro se ha borrado y el contador se ha actualizado.
También marcha adecuadamente cuando se borran los otros 2 registros sobrantes mediante una consulta de tipo DQL.
Ahora que se han borrado todos los posts relacionados, el valor de la columna
num_posts
deberí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 con mucho tráfico es necesario guardar la información encaches para calmar algunos recursos de la CPU. En la última versión de doctrine doce se han añadido muchas mejoras a la cache de resultados para tenerun mejor control sobre el borrado de las entradas de la cache. Antes no se podíaespecificar la clave asociada con cada entrada de la cache, con lo que no eraposible identificar correctamente la entrada que se quería borrar.
En esta sección se muestra un caso fácil de cómo utilizar la cache de resultados para guardar en ella todas las consultas relacionadas con losusuarios, así como el empleo de acontecimientos para borrar todas y cada una de las entradas cuyainformación haya sido cambiada.
El siguiente esquema es el que se va a usar en este ejemplo:
A continuación se crean todas las clases con el siguiente comando:
Después de ejecutarla, se habrá generado la siguiente clase llamada
User
:
Más adelante se añadirá el código pertinente en esta clase, así que no lapierdas de vista.
Antes de emplear 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 se hace uso del driver APCporque es la mejor elección para los ambientes de producción. Si no dispones deAPC, puedes utilizar los drivers
Doctrine_Cache_Db
o
Doctrine_Cache_Array
para 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 guardar 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 toda vez que se modifica alguna información del usuario.
La siguiente consulta se puede utilizar para mostrar una lista completa de todos y cada uno de los usuarios ordenados alfabéticamente:
Para guardar el resultado de esa consulta en la cache, se emplea el método
useResultCache()
:
note
El tercer argumento del método es fundamental, 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 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 vez de realizar 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, 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
:
Ahora que la consulta ya se ha guardado en la cache, el siguiente paso consisteen aprender a borrar esa cache. El borrado se puede realizar manualmente conla API del driver de la cache o bien se pueden emplear los acontecimientos para borrar lacache automáticamente cuando se introduce o bien altera un usuario.
La API del driver de la cache
Antes de emplearla en un acontecimiento, se va a mostrar el empleo manual de la API deldriver de la cache.
tip
La instancia del driver de la cache se puede obtener a través de la instancia de la clase
Doctrine_Manager
.
Si no está definida la variable
$ manager
, puedes obtener la instancia pertinente con el siguiente código.
Ahora ya se puede hacer empleo de la API para borrar las entradas de la cache:
Seguramente la cache contendrá más de una consulta relacionada con losusuarios y todas harán empleo del mismo prefijo
users_
así que el método
delete()
no es muy útil en este caso. En su lugar se puede emplear el método
deleteByPrefix()
para borrar la cache de todas las consultas que contengan elprefijo indicado:
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 elsufijo indicado. -
deleteByRegex($ 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 y cada una de 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 ello, sólo es necesarioconfigurar un acontecimiento en el método
postSave()
de la clase del modelo
User
.
¿Recuerdas la clase
User
creada anteriormente? Abre la clase con tu editorfavorito y añade el código del siguiente método
postSave()
:
Ahora, cada vez que se actualiza un usuario y cada vez que se introduce un nuevousuario, se borran de la cache todas las consultas relacionadas con los usuarios:
Después de ejecutar el código precedente, la próxima vez que se efectúen las consultasde los usuarios no existirá una cache con los resultados, por lo que se volverána realizar 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 muy sencillo, es útil para hacerse una idea de cómo se puede utilizar esta característica de Doctrine para tener un controlmuy preciso de la forma en la que se guardan las consultas en la cache.
Una de las primordiales características de Doctrine en su habilidad paratransformar un objeto de tipo
Doctrine_Query
en resultados con diferentesestructuras. Esta labor la efectúan los
hydratorsde Doctrine y hasta laversión doce de Doctrine los programadores no podían crear sus
hydrators.Ahora que es posible hacerlo, se puede desarrollar un
hydratorpropio paracrear cualquier tipo de estructura desde los resultados obtenidosmediante
Doctrine_Query
.
El siguiente ejemplo muestra cómo crear un hydratormuy sencillo y fácil de entender, mas a la vez muy útil. El funcionamiento del hydratorconsiste enseleccionar dos 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 realizar las pruebas se marcha a utilizar el siguiente esquema de un modelosencillo llamado
User
:
Como también son necesarios algunos datos de prueba, se va a hacer empleo de lossiguientes:
A continuación ejecuta la siguiente labor para crear todas las clases:
Para crear un
hydratorsólo es necesario crear una nueva clase que herede de
Doctrine_Hydrator_Abstract
y que implemente un método llamado
hydrateResultSet( dólares americanos stmt)
.Este método recibe como razonamiento una instancia del
PDOStatement
utilizado paraejecutar la consulta. Por lo tanto, se puede usar este objeto para obtener losresultados de la consulta de forma directa del PDO y convertirlos en la estructuradeseada.
Se crea una nueva clase llamada
KeyValuePairHydrator
y se pone en el directorio
lib/
para que symfony pueda cargarla automáticamente:
El código precedente de momento sólo devuelve los datos tal como los devuelve PDO. Esto no es lo que deseamos, en tanto que queremos transformar losdatos en una estructura de tipo
clave => valor
. Modifica por ende 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 utilizar el
hydratores necesario registrarlo en Doctrine para queesté disponible cuando se ejecuten las consultas. Para ello, regístralo en la instancia del
Doctrine_Manager
en la clase
ProjectConfiguration
:
Ahora que el
hydratorya está registrado, se puede utilizar en cualquierinstancia de
Doctrine_Query
, como muestra el próximo ejemplo:
Si se ejecuta el código precedente con los datos de prueba mostrados previamente,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.