Symfony y Doctrine

Symfony y Doctrine

15 Jul 2020 in

Una de las labores más comunes y difíciles en cualquier aplicación es el tratamiento de información con una base de datos. La versión Symfony Standard Editionintegra el ORM Doctrinepor defecto, librería cuyo objetivo es suministrar herramientas potentes para el tratamiento de la información.

Doctrine deja mapear objetos a una base de datos relacional, como MySQL, PostgreSQLo MicrosoftSQL, aunque también se puede hacer con MongoDBcon la librería Doctrine ODMy el bundle.

Vamos a ver un ejemplo de un objeto Product. Primero configuraremos la base de datos, después vamos a crear el objecto Product, lo incluiremos en la base de datos ( persist) y finalmente lo extraeremos ( fetch).

1) Configurar la base de datos

Primero se tiene que configurar adecuadamente la conexión a la base de datos. Por convención esta información se configura generalmente en un fichero app/config/parameters.yml:

Se emplea parameters.yml para esta información por el hecho de que así puedes mantener diferentes versiones del fichero para cada servidor. Puedes también guardar información de la configuración de la base de datos fuera del proyecto, como dentro de la configuración de Apache, por ejemplo ().

Ahora que doctrine conoce la base de datos, puedes hacer que la cree:

2) Crear una clase Entity

Se supone que creamos una aplicación donde los productos se mostrarán. Sin pensar en Doctrine o bien en bases de datos, ya sabemos que precisamos un objeto Product para representar estos productos. Creamos la clase en el directorio Entity en el AppBundle:

La clase, normalmente llamada "entity", que significa una clase básica que guarda datos, es simple y ayuda a la administración de los productos en la compañía. Esta clase no se puede persistir en la base de datos todavía.

Se puede emplera un comando que te guiará pasito a pasito con la creación de una entidad:

3) Añadir información de mapeo

Doctrine deja trabajar con bases de datos de una manera más especial que sencillamente pasando filas de una tabla a un array. En su sitio, Doctrine deja persistir objetos enteros en la base de datos y extraer objetos enteros. Se enlaza una clase PHP con una tabla de base de datos, y las propiedades de esa clase PHP con las columnas de la tabla:

Para que Doctrine pueda hacer esto, sencillamente debes crear "metadatos", o configuración que le dice a Dcotrine exactamente cómo la clase Product y sus propiedades han de enlazarse a la base de datos. Estos metadatos pueden especificarse con diferentes formatos como YAML, XML, o bien de manera directa en la clase Product con anotaciones:

Un bundle puede aceptar sólo un tipo de formato de información de metadatos. No es posible entremezclar metadatos procedentes de YAMLy de PHP, por ejemplo.

El nombre de la tabla es opcional, y si se omite, se determinará automáticamente basándose en el nombre de la clase entity.

Doctrinepermite seleccionar entre una gran pluralidad de campos, cada uno con sus propias opciones. Puedes ver su documentación sobre el mapeo de datos.

Hay que tener cuidado con no mapear los nombres de clases o propiedades con palabras protegidas de SQL (como groupo user).Puedes leer más sobre las palabras reservadas.

4) Generar getters y setters

Incluso cuando Doctrinesabe cómo persistir un objeto Product en la base de datos, la clase en sí no es verdaderamente útil todavía. Ya que Product es sencillamente una clase PHP normal, precisas crear métodos gettery setter(como getName()o setName()) para acceder a las propiedades (en tanto que las propiedades son protected). Puedes hacerlo automáticamente con el próximo comando:

Este comando asegura que se producen todos los getters y setters para la clase Product (si ya está generado, no lo repite).

Con el comando doctrine:generate:entities se puede:

  • Generar getters y setters
  • Gerarar clases repositorio configuradas con la anotación @ORM\Entity
  • Generar el constructor apropiado para relaciones 1:n y n:m

El comando produce un backup de Productpor si quizás, llamado Product.php~. En algunos casos este archivo puede ocasionar un error " Cannot redeclare class". Puedes borrarlo o usar la opción --no-backuppara prevenir la generación de estos archivos backup.

También puedes producir todas las entidades de vez de un bundle o bien incluso de un namespace:

5) Crear las tablas de la base de datos

Ahora tienes una clase usable Product con información de mapeo de manera que Doctrine sabe como persistir en la tabla pertinente. De momento no tenemos la tabla product en la base de datos. Doctrine puede crear automáticamente las tablas para cualquier entidad famosa en tu aplicación:

Este comando es muy potente, equipara cómo debería verse (basándose en la información de las entidades) como cómo se ve verdaderamente, y genera las sentencias SQL necesarias para actualizar la base de datos y formarla adecuadamente. Si añades una nueva propiedad en Product y ejecutas nuevamente este comando, generará la sentencia ALTER TABLE precisa para añadir la nueva columna en la tabla existente product.

Una forma incluso mejor para beneficiarse de esta funcionalidad es a través de migraciones, que permiten producir estas sentencias SQL y guardarlas en clases migrationque pueden ejecutarse sistemáticamente en tu servidor de producción para migrar tu esquema de base de datos de manera segura.

6) Persistir objetos en la base de datos

Ahora que hemos enlazado la entidad Productcon la tabla correspondiente product, podemos persistir datos en la base de datos. Desde un supervisor es bastante sencillo. Vamos a añadir un método para crear un producto en ProductController:

Este ejemplo utiliza el método getDoctrine()que hereda de la clase base Controller. Este método es un shortcut para conseguir el service doctrine. Puedes emplear Doctrine en otro lado inyectando ese servicio.

El método persist()le afirma a Doctrineque administre el objeto dólares americanos product. Esto todavía no ejecuta ninguna sentencia en la base de datos.

Cuando se llama al método flush(), Doctrine mira entre los objetos que administra si hay alguno que también requiera ser persistido. En este caso, el objeto $ productno se ha persistido todavía, por lo que el entity mánager ejecuta una sentencia INSERT y se crea una fila en la tabla product.

Ya que Doctrine está pendiente de todas las entidades administradas, cuando llamas al método flush()ejecuta las sentencias en el orden correcto. Utilizacacheadas para prosperar el desempeño. Por servirnos de un ejemplo, si persistes un total de 100 objetos Producty llamas a flush(), Doctrine ejecutará cien sentencias INSERT en una sóla sentencia preparada.

Cuando se crean o actualizan objetos, el workflow es siempre el mismo. Si a Doctrine se le afirma que cree un nuevo registro que ya existe, sabe actualizarlo, a través de una sentencia UPDATE en vez de INSERT.

Doctrine da una librería que permite cargar datos de testing programáticamente en el proyecto, con el.

7) Extraer objetos de la base de datos (fetch)

Extraer un objeto de la base de datos es muy sencillo. En el mismo controller ProductControllercreamos el método showAction():

Cuando solicitas un tipo particular de objeto, siempre y en toda circunstancia empleas lo que es conocido como repositorio. Un repositorio es una clase PHP cuyo único trabajo es asistir a extraer entidades de una clase determinada. Puedes acceder al objeto repositorio para una clase entidad así:

El string AppBundle:Product es un shortcut que se puede emplear en cualquier parte en Doctrine en lugar de utilizar la clase entera de la entidad (AppBundle\Entity\Product). Toda vez que tu entidad esté en el namespace Entity de tu bundle.

Una vez que tienes el repositorio, tienes acceso a múltiples métodos:

Con los métodos findBy()y findOneBy()puedes extraer objetos bajo múltiples condiciones:

Cuando renderizas cualquier página, puedes ver cuantas queries se han realizado en la barra debug. Si haces clic en el icono que muestra las queries, se abrirá el profiler, mostrando las consultas exactas que se hayan hecho.

El icono se mostrará amarillo si hubo más de cincuenta consultas en la página. Esto podría apuntar que algo no va bien.

8) Actualizar un objeto

Una vez que has extraído un objeto de Doctrine, actualizarlo es fácil. Creamos el método updateAction():

Actualizar un objeto se hace en 3 pasos:

  1. Extraer el objeto de Doctrine
  2. Modificar el objeto
  3. Llamar a flush()con el entity manager

Llamar a dólares americanos em->persis( dólares americanos product)no es necesario. Este método simplemente le dice a Doctrine que administre o bien que vigile el objeto dólares americanos product. En este caso, como ya trajimos el objeto $ product de Doctrine, ya está administrado.

9) Borrar un Objeto

Borrar un objeto es muy afín, mas requiere una llamada al método remove()del entity mánager.

El método remove() avisa a Doctrine que quieres remover el objeto de la base de datos pero la consulta DELETE no es ejecutada hasta el momento en que lleva por nombre al método flush().

10) Consultar objetos

Ya has visto como el objeto repositorio permite has consultas básicas sin hacer nada.

Pero Doctrine también deja realizar consultas más completas usando el Doctrine Query Language (DQL). DQL es afín a SQL pero tienes que imaginar que estás haciendo una consulta para uno o más objetos de una clase entity (como Product) en vez de consultar filas de una tabla.

Cuando se hacen consultas se tienen dos opciones: Doctrine querieso Doctrine Query Builder.

11) Consultar objetos con DQL

Queremos consultar productos, mas enseñar sólo aquellos que cuestan más de 19.99 , ordenados del más barato al más costoso. Podemos emplear DQL:

Si tienes costumbre de utilizar SQL, DQLresultará sencillísimo. La diferencia más grande es que tendrás que meditar en términos de objetosen lugar de filasen una base de datos. Escogemos desde el objeto AppBundle:Producty le pones un alias p.

Observa el método setParameter(). Cuando se trabaja con Doctrine siempre y en todo momento es una buena idea establecer valores externos como placeholders( :priceen el ejemplo precedente) en tanto que previene ataques SQL injection.

El método getResult()devuelve un array de resultados. Para obtener sólo uno se puede emplear getOneOrNullResult():

DQL es muy potente, permitiéndote también unir entidades, reunir, etc. Puedes ver más en la.

12) Consultar objetos con Doctrine Query Builder

En sitio de redactar un string DQL, puedes emplear el objeto QueryBuilderpara que lo edifique. Es útil cuando la consulta depende de condiciones dinámicas, cuando tu código emplieza a complicarse con DQL y comienzas a concatenar strings:

El objeto QueryBuildercontiene cada método necesario para edificar la consulta. Llamando al método getQuery()el query builder devuelve un objeto normal Query, que puede emplearse para obtener el resultado del query. Puedes leer la.

13) Clases repositorio personalizadas

En las secciones precedentes hemos contruído y utilizado consultas más complicadas desde un controller. Para aislarlas, testar y reutilizar estas consultas, es una buena práctica crear una clase repositorio para la entidad y añadir métodos con la lógica de las consultas ahí.

Para hacerlo, añade el nombre de la clase repository a la definición del mapeo:

Doctrine puede generar la clase repository ejecutando el comando de antes:

Ahora añadimos un nuevo método findAllOrderedByName()en el archivo generado. Este método consultará todas y cada una de las entidades Product, ordenadas alfabéticamente.

Puede accederse al entity manager desde el repositorio con $ this->getEntityManager().

Ahora podemos emplear ese método como los métodos por defecto del repositorio:

14) Relaciones/Asociaciones entre entidades

Suponemos que los productos de la aplicación pertenecen todos a una categoría. En este caso necesitaremos un objeto Category y una forma de relacionar un objeto Product con un objeto Category. Primero creamos la entidad Category. Podemos hacerlo con el próximo comando:

Ahora ya tenemos una entidad Category, con un campo id, un campo namey los métodos gettery setterasociados.

Para relacionar las entidades Category y Product, creamos una propiedad products en la clase Category:

Ya que el objeto Category va a referenciar a múltiples objetos Product, una propiedad array de products se añade para guardar esos objetos Product.

El código en el método __construct es importante porque Doctrine requiere que la propiedad $ products sea un objeto ArrayCollection. Esto objeto semeja y actúa prácticamente igual que un array, mas añade alguna flexibilidad.

El valor targetEntitypuede referenciar a una entidad con un namespace válido, no sólo entidades definidas en el mismo namespace.

Ya que cada objecto Productpuede hacer referencia a sólo una categoría, añadimos la propiedad dólares americanos categoryen la clase Product:

Ahora que hemos añadido una nueva propiedad tanto en la clase Categorycomo en la clase Product, le afirmamos a Doctrine que genere los métodos gettes y setter:

Ahora tenemos 2 clases, Category y Product con una relación natural one-to-many. La clase Category almacena un array de objetos Product y el objeto Product puede almacenar un objeto Category. En otras palabras, has construído las clases de forma que tiene sentido para tus requisitos. El hecho de que los datos deben ser persistidos en la base de datos es siempre y en todo momento secundario.

Si miramos a los metadatos sobre la propiedad $ categoryen la clase Product, esa información le dice a Doctrineque la clase relacionada es Categoryy que debería guardar el id del registro de la caegoría en un campo _category iden la tabla product. En otras palabras, el objeto Categoryrelacionado se guardará en la propiedad $ category, pero Doctrine lo que hará será persistir la relación guardando el valor id de la categoría en una columna _category idde la tabla product.

Los metadatos sobre la propiedad dólares americanos products del objeto Category es menos esencial, y simplemente le afirma a Doctrine que mire en la propiedad Product.category para aveirguar cómo está mapeada la relación.

Antes de proseguir hay que cerciorarse de que doctrine añade la nueva tabla category, y la columna product.category_id y una nueva foreign key:

Este comando sólo debería emplearse durante el desarrollo. Para un método más robusto de actualización sistemática en una base de datos en producción, es mejor emplear.

15) Guardar entidades relacionadas

Ahora podemos crear una categoría, un producto, y asignar el producto a una categoría:

16) Extraer objetos relacionados

Cuando necesitamos extraer objetos asociados, el workflow es igual que ya antes. Primero extraemos un objeto $ product y después accedemos a su categoría relacionada:

En este ejemplo primero consultamos un objeto Productbasándonos en el iddel producto. Esto emite una consulta sólo para los datos del producto y rellena el objeto $ productcon esos datos. Después, cuando llamamos a dólares americanos product->getCategory()->getName(), Doctrinehace una segunda consulta para hallar la categoría que está relacionada con este producto. Prepara al objeto $ categoryy lo devueleve.

Lo que es esencial es el hecho de que tienes fácil acceso a la categoría relacionada con el producto, pero los datos de la categoría no son extraídos hasta que no solicitas la categoría (esto lleva por nombre lazy loading).

También podemos consultar al revés:

En este caso ocurre lo mismo: primero consultas un simple objeto Category, y después Doctrine hace una segunda consulta para extraer los objetos Product relacionados, pero sólo una vez. La variable $ productses un array de todos y cada uno de los objetos Product que están enlazados al objeto Category dado a través del valor _category id.

17) Relaciones y clases Proxy

El lazy loadinges posible pues, cuando es preciso, Doctrine devuelve un objeto proxy en lugar del objeto real. En el ejemplo precedente, si hacemos dumppara obtener la clase de dólares americanos category:

Este objeto proxy extiende el objeto Category, y semeja y actúa como él. La diferencia radica en que utilizando un objeto proxy, Doctrine puede retrasar la consulta de los datos Categoryreales hasta que verdaderamente necesites esos datos (por servirnos de un ejemplo hasta el momento en que llames a $ category->getName()).

Las clases proxy son generadas por Doctrine y se guardan en el directorio cache. Y aunque no te percates de que un objeto dólares americanos category es realmente un objeto proxy, es esencial tenerlo en consideración.

En la próxima sección, cuando extraigamos los datos de producto y categoría de vez con un join, Doctrine devolverá el objeto Category de verdad, ya que nada debe ser cargado de forma lazy.

18) Unir registros relacionados

En los ejemplos precedentes se han hecho 2 consultas, una para el objeto original (por servirnos de un ejemplo, Category) y otra para el objeto relacionado (los objetos Product).

Si de primeras ya sabes que necesitarás acceder a ambos objetos, puedes eludir la segunda consulta con un join en la consulta original mediante un método:

Ahora podemos utilizar este método en el controller para preguntar para un objeto Producty su categoría relacionada con una sóla consulta:

Para más información sobre asociaciones puedes leer la.

19) Lifecycle Callbacks

A veces necesitamos hacer una acción inmediatamente antes o bien después de que una entidad es insertada, actualizada o bien eliminada. Estos géneros de acciones son conocidos como lifecycle callbacks, ya que son métodos callback que precisas ejecutar durante los diferentes estados del ciclo vital de una entidad.

Si usamos anotaciones para los metadatos, debemos activar los lifecycle callbacks así:

Ahora podemos decirle a Doctrine que ejecute un método en cualquiera de los eventos lifecycle disponibles. Por servirnos de un ejemplo, queremos crear una columna createdAt con la fecha actual, sólo cuando la entidad es persistida (insertada) por vez primera (se supone que hemos creado y mapeado la propiedad createdAt):

Ahora, justo antes de que la entidad sea persistida por primera vez, Doctrine llamará automáticamente a este método y el campo createdAtse establecerá en la data actual.

Puedes ver más eventos lifecycle en la.

Se puede ver que el método setCreatedAtValue()no recibe ningún razonamiento. Este es siempre y en toda circunstancia el caso para los lifecycle callbacks y es intencional: los lifecycle callbacks deberían ser simples métodos que tienen en cuenta la transformación interna de datos en la entidad (por ejemplo establecer un campo created/updated, generar un valor slug, etc).

Si lo que se quiere es hacer algo más pesado, como loggingo enviar un email, deberías registrar una clase externa como event listener o bien subscriber y darle acceso a los recursos que sean necesarios.

Fuentes:

Back to top
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