Uso avanzado de Doctrine

Uso avanzado de Doctrine

15 Jul 2020 in

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 actAsdel 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_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 altera 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 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. En consecuencia, en este caso se añade el comportamiento en el modelo Postpara su relación Thread. De esta forma, el número de posts de cualquier Threadse almacena 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 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 CountCacheListenerque extiende la clase Doctrine_Record_Listenery 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 Threadcuyo idvalga 1incrementará 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_postsse 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 DELETEy la convierte enuna consulta SELECTque deja obtener 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 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_postsdel modelo Threadvale 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_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 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_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 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 Usercreada 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_Queryen 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_Abstracty que implemente un método llamado hydrateResultSet( dólares americanos stmt).Este método recibe como razonamiento una instancia del PDOStatementutilizado 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 KeyValuePairHydratory 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_Manageren 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.

Share icon

ESTOS EXCLUSIVOS INFORMES GRATUITO REVELAN

7 SECRETOS DE EXPERTOS SEO QUE TE LLEVÁN AL 1#
7 SECRETOS DE EXPERTOS SEO QUE TE LLEVÁN AL 1# EN GOOGLE PARA GANAR 10.000s DE TRÁFICO DE CALIDAD GRATUITO - EN SÓLO 2 MESES
 

Los 7 pasos más poderosos para disparar tu ranking orgánico para ALCANZAR Y MANTENER un impresionante tráfico orgánico es TUYO.

Consigue gratis lo que el 1% de los expertos en SEO venden por miles de euros... y el otro 99% ni siquiera sabe que existe.


OBTEN MI INFORME GRATUITO
5 errores que debes evitar en tu sitio web de Drupal
Ebook - 5 errores que debes evitar en tu sitio web de Drupal (¡podrían costarte miles de euros!)
 

Este Ebook cubre 5 terribles errores que probablemente estés cometiendo ahora mismo con tu sitio web de Drupal.

¡Nº3 TE SORPRENDERÁ! Esta lectura de 10 minutos te ahorrará miles de euros.



OBTEN MI INFORME GRATUITO