¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí

Modelización de datos con Doctrine

Introducción

Después de haber explorado los controladores y las vistas, ahora nos centraremos en una parte muy importante de nuestra aplicación: el modelo de datos.

La mayoría de las veces (aunque no es obligatorio), los datos se almacenan en bases de datos. Veamos cómo trabaja Symfony con una base de datos.

Las bases de datos

¿Qué es una base de datos?

Básicamente, son archivos en los cuales los datos están estructurados de manera que se puedan extraer rápidamente y de manera segura.

Los datos están organizados como un conjunto de tablas. Estas tablas se asemejan a hojas de cálculo de Excel. Tienen un conjunto de columnas (llamadas campos) y un conjunto de filas (llamadas registros).

He aquí un ejemplo de tabla de datos:

images/RI15_02.png

Las tablas de datos suelen tener un índice (en este caso, es id_coche) que mejora el rendimiento de la búsqueda en la tabla.

Existen diferentes tipos de bases de datos:

  • MySQL (o su equivalente hoy en día, Maria DB)

  • Oracle (la estructura más completa, pero de pago)

  • PostgreSQL (utiliza tipos de datos modernos)

  • SQL Server (la base de datos de Microsoft, de pago)

El lenguaje SQL

Todas las acciones en una base de datos se realizan con el lenguaje SQL.

Existen variantes de este lenguaje según el sistema de base de datos: MySQL, SQL Oracle, SQL Server... pero las instrucciones son muy similares.

Vamos a utilizar el servidor de base de datos proporcionado por WAMP (o equivalente: XAMPP en Linux y MAMP en macOS). El lenguaje utilizado para estos servidores es MySQL. Probablemente oirá decir que MariaDB es el equivalente de MySQL.

No vamos a enseñar este lenguaje aquí, ya que no es el objetivo del libro. Si desea obtener más información, puedes visitar el sitio de MySQL:

https://dev.mysql.com/doc/refman/8.0/en 

Solo necesita saber que el lenguaje MySQL funciona con consultas de datos.

Una consulta es una instrucción que utiliza el lenguaje MySQL.

Veamos un ejemplo.

Para recuperar los datos de la tabla Coche que mostramos antes, debemos ejecutar la consulta:

SELECT marca,modelo,color FROM coche; 

También podemos insertar un nuevo registro en la tabla:

INSERT INTO coche (marca, modelo, color) VALUES ('Porsche', 
'Carrera', 'negro'); 

o modificar un registro:

UPDATE coche SET color='azul' WHERE marca='Porsche'; 

Y, finalmente, podemos eliminar un registro:

DELETE FROM coche WHERE marque='Porsche'; 

El lenguaje SQL puede ejecutarse a través del lenguaje PHP. Sin embargo, en Symfony, en la mayoría de los casos, no será...

El ORM de Symfony: Doctrine

Doctrine es el intermediario entre su aplicación y las bases de datos. Es compatible con todos los lenguajes: MySQL, PostgreSQL, etc. Se trata de una capa intermedia que le permite evitar el uso directo de los lenguajes de gestión de bases de datos. Doctrine también proporciona seguridad contra las vulnerabilidades que suelen afectar principalmente al acceso a las bases de datos. Por lo tanto, se recomienda su uso.

Doctrine es una herramienta independiente de Symfony. Usted puede instalar Doctrine directamente utilizando un script PHP estándar.

La documentación oficial de Doctrine se encuentra en: https://www.doctrine-project.org/projects/doctrine-orm/en/current/tutorials/getting-started.html

En Symfony, Doctrine viene instalado por defecto. No es necesario cargar paquetes adicionales.

La configuración de Doctrine se realiza a través de la variable de entorno DATABASE_URL, en el archivo .env:

DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/ 
db_name?serverVersion=5.7 

Usted puede configurar .env para acceder a otras bases de datos, como MariaDB, SQLite, PostgreSQL u Oracle, según sus necesidades. Consulte la página: https://symfony.com/doc/current/doctrine.html

Vamos a configurar Doctrine para usar el servidor de bases de datos de WAMP. Es un servidor MySQL. La dirección del servidor es: 127.0.0.1:3306. Para verificarlo, haga clic en el botón verde...

Las entidades

Como mencionamos, vamos a utilizar clases PHP para crear las tablas en la base de datos. No será necesario escribir consultas MySQL, ya que Symfony generará automáticamente estas consultas.

Estas clases un tanto especiales se llaman entidades.

Usted puede crear las entidades manualmente, pero lo mejor es utilizar un comando en la línea de comandos del terminal:

php bin/console make:entity 

Un sistema de preguntas y respuestas le permitirá construir su entidad.

Vamos a crear, por ejemplo, una entidad Producto que contendrá un nombre, una cantidad, un precio y una variable booleana agotado (que indicará si el producto está agotado o no).

Estas son las respuestas que hay que proporcionar a las preguntas en el terminal:

php bin/console make:entity  
Class name of the entity to create or update (e.g. BravePuppy):  
> Producto  
Add the ability to broadcast entity updates using Symfony UX 
Turbo? (yes/no) [no]:no  
New property name (press <return> to stop adding fields):  
> nombre  
Field type (enter ? to see all types) [string]:  
> string  
Field length [255]:  
> 200  
Can this field be null in the database (nullable) (yes/no) [no]:  
> no  
Add another property? Enter the property name (or press <return>  
to stop adding fields):  
> precio  
Field type (enter ? to see all types) [string]:  
> float  
Can this field be null in the database (nullable) (yes/no) [no]:  
> no  
Add another property? Enter the property name (or press <return> 
to stop adding fields):  
> cantidad  
Field type (enter...

Las migraciones

Para crear la tabla en la base de datos, necesitamos pasar por una migración. Una migración es una clase que describe cómo realizar la operación.

Crear las migraciones de todas las entidades creadas resulta muy fácil. Simplemente, ejecute en el terminal el comando:

php bin/console make:migration 

Todas las entidades creadas se examinan para generar las migraciones correspondientes.

Si abre la carpeta migrations, encontrará un archivo cuyo nombre se parece a: Versionnumerodeversion.php, donde numerodeversion es un número entero único generado a partir de la fecha, la hora, los minutos y los segundos del momento presente.

Este archivo es único para cada entidad.

Contiene una clase que tiene dos métodos: up() y down(). El método up() contiene la consulta SQL que generará o modificará la tabla correspondiente a la entidad.

En este caso, se trata de crear la tabla producto. Encontramos en este método la consulta SQL que permite crear la tabla:

  public function up(Schema $schema): void  
    {  
        $this->addSql('CREATE TABLE producto (id INT AUTO_INCREMENT 
NOT NULL, nom VARCHAR(200) NOT NULL, precio DOUBLE PRECISION NOT 
NULL, cantidad INT NOT NULL, agotado TINYINT(1) NOT NULL, 
PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE 
`utf8mb4_unicode_ci`...

Las fixtures

Las fixtures nos permitirán llenar nuestra tabla de productos mediante un comando en el terminal. Dicho esto, lo que vamos a escribir en la fixture puede colocarse igualmente en la acción de un controlador.

La única ventaja de las fixtures es que hay un comando en el terminal que permite ejecutar todas las fixtures existentes de una sola vez.

Para usar las fixtures, es necesario instalar un paquete adicional. Si se dirige al Symfony Recipes Server: https://github.com/symfony/recipes/blob/flex/main/RECIPES.md y busca «fixture», encontrará este paquete: doctrine/doctrine-fixtures-bundle.

Vemos que podemos utilizar el alias orm-fixtures (u ormfixtures).

Lo añadiremos desde el terminal:

php composer.phar require --dev orm-fixtures 

El comando ha creado una carpeta src/DataFixtures y, dentro de ella, un archivo de ejemplo AppFixtures.php.

Todas las fixtures heredan de la clase Fixture del FixtureBundle, que es una clase abstracta (ver capítulo Lenguaje orientado a objetos, sección Las clases abstractas y las interfaces - Las clases abstractas). El método load() de la clase Fixture también es abstracto, por lo que será necesario volver a definirlo en todas las fixtures.

Es este método el que se ejecutará automáticamente cuando lancemos las fixtures. 

Vamos a crear una fixture propia para nuestra entidad Producto.

Cree, en src/DataFixtures, un archivo ProductoFixtures.php y pegue el contenido de AppFixtures.php.

Luego, renombre la clase ProductoFixtures:

<?php  
  
namespace App\DataFixtures;  
  
use Doctrine\Bundle\FixturesBundle\Fixture;  
use Doctrine\Common\Persistence\ObjectManager;  
  
class ProductoFixtures extends Fixture  
{  
    public function load(ObjectManager $manager)  ...

La recuperación de los datos a partir de la base

Hemos visto cómo guardar los datos. La recuperación también resulta fácil gracias a Doctrine.

Para esto, vamos a crear un nuevo controlador llamado ListaProductosController. Recuerde cómo creamos un controlador:

php bin/console make:Controller ListaProductos 

Eliminamos la acción predeterminada index() y creamos una acción listar() con la ruta /listar:

<?php  
  
namespace App\Controller;  
  
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;  
use Symfony\Component\HttpFoundation\Response;  
use Symfony\Component\Routing\Annotation\Route;  
  
class ListaProductosController extends AbstractController  
{  
    #[Route('/listar', name: 'listar')]  
    public function listar()   
    {  
        return $this->render('lista_productos/index.html.twig', [ 
            'controller_name' => 'ListaProductosController',  
        ]);  
    }  
  
} 

Para recuperar los datos de la tabla producto, vamos a utilizar el archivo Repository: src/Repository/ProductoRepository.php.

Este archivo puede ser recuperado a partir de la entidad Producto. Para hacerlo, necesitamos identificar la clase Producto:

use App\Entity\Producto; 

Luego, recuperamos el EntityManager:

use Doctrine\ORM\EntityManagerInterface; 

E inyectamos esto en los parámetros de la función listar():

public function listar(EntityManagerInterface $entityManager)  
{  
...  
} 

Y, finalmente, en la función listar(), recuperamos el Repository:

$productosrepository=$entityManager->getRepository(Producto::class); 

El objeto $productosrepository hereda de una clase madre ServiceEntityRepository, que posee los métodos para recuperar los datos.

Por ejemplo, el método findAll() buscará todos los registros de la tabla:

  $listaProductos=$productosrepository->findAll(); 

$listaProductos será un objeto de tipo ArrayCollection (una colección...

Los métodos del Repositorio

Hemos visto que, para recuperar datos de la base, se deben utilizar los métodos heredados del Repositorio.

El método findAll() permite recuperar todas las entidades de una tabla. Este método devuelve un objeto ArrayCollection.

Un objeto ArrayCollection depende de una clase que contiene una propiedad de tipo Array, incluyendo la lista de entidades. La ventaja de esta clase (en lugar de un simple Array de PHP) es que proporciona un conjunto de métodos para recorrer el arreglo de datos.

Puede ver los diferentes métodos de esta clase.

Para encontrar una clase en VSCode, pulse [Ctrl] y P, luego escriba el nombre de la clase; por ejemplo, aquí, Collection. Debería encontrar automáticamente el contenido de la clase.

Si no la encuentra, aquí tiene su ubicación: vendor/doctrine/collections/lib/Doctrine/Common/Collections/ArrayCollection.ph

Existen otros métodos de recuperación de entidades. Para obtener la lista, puede confiar en esta página de Doctrine: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-objects.html#querying

Allí encontrará métodos como:

  • find(id): devuelve la entidad según su id.

  • findBy(array(’agotado’ => false)): devuelve las entidades según el valor de una propiedad (aquí, agotado = false)....

El lenguaje DQL

Doctrine Query Language es similar a SQL, con la diferencia de que se aplica a las entidades (objetos PHP) en lugar de a la base de datos.

Encontrará toda la sintaxis de DQL en la página: https://www.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html

El uso de DQL se realiza en un método del Repositorio a partir del método prepare() del EntityManager.

Sintaxis:

$query = $em->createQuery('Consulta DQL ');  
$resultados = $query->getResult();  

El método getResult() recupera todos los resultados de la consulta.

Hay otros métodos de recuperación posibles.

  • getSingleResult(): devuelve un solo objeto. Si la consulta3 devuelve varios objetos, aparecerá un error. Si la consulta no devuelve ningún objeto, también aparecerá un error.

  • getOneOrNullResult(): devuelve un solo objeto. Si la consulta devuelve varios objetos, aparecerá un error. Si la consulta no devuelve ningún objeto, se devolverá un valor nulo.

  • GetArrayResult(): devuelve los resultados como matrices anidadas en lugar de devolver un ArrayCollection.

  • GetScalarResult(): devuelve valores escalares que pueden contener datos duplicados.

  • GetOneScalarResult(): devuelve un solo valor escalar.

Veamos un ejemplo.

Deseamos colocar los últimos productos registrados en nuestro sitio en primer lugar, en la parte superior...

El Query Builder

El Query Builder es la segunda forma de personalizar la recuperación de datos. Permite ejecutar solo métodos de clase para generar la consulta. Permanecemos en la lógica orientada a objetos de PHP, sin necesidad de escribir una consulta «en duro».

El Query Builder es un método del Repositorio al que podemos acceder directamente.

Sintaxis:

$this->createQueryBuilder('alias de la entidad')-> 
métodos del Query Builder 

Encontrará toda la sintaxis del Query Builder en la página: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/query-builder.html#the-querybuilder

Los métodos de recuperación de resultados son los mismos que para DQL.

Veamos un ejemplo.

Deseamos mostrar una promoción para el último producto insertado en la base (para darlo a conocer).

Crearemos un método getLastProducto() en el archivo Repository/ProductoRepository.php. Esta vez, utilizamos el Query Builder para ejecutar la consulta.

Utilizaremos el método setMaxResults(1), que devuelve el primer elemento devuelto por la consulta, y usaremos getOneOrNullResult() para recuperar el resultado.

El método getLastProducto() se escribe así:

 public function getLastProducto()  
    {  
  
        $lastProducto=$this->createQueryBuilder('p')  
        ->orderBy('p.id', 'DESC')  
        ->setMaxResults(1)  
        ->getQuery()  
        ->getOneOrNullResult();  ...

Cómo lanzar consultas SQL

Es posible realizar muchas cosas con DQL o el Query Builder. Estos métodos no se limitan a la consulta de datos; también se pueden usar para insertar, modificar o eliminar registros.

Sin embargo, para aquellos con consultas grandes que serían difíciles de implementar con estos métodos, Doctrine les da la posibilidad de ejecutar consultas SQL directamente.

Basta con recuperar la conexión a la base de datos desde el EntityManager, preparar la consulta y ejecutarla. La recuperación de datos se realiza con el método fetchAllAssociative().

Por ejemplo, podemos reemplazar el DQL del método orderingProducto() con una consulta SQL equivalente:

    public function orderingProducto() 
    {  
        $conn = $this->getEntityManager()->getConnection(); 
  
        $sql="SELECT * FROM producto ORDER BY id DESC"; 
        $stmt = $conn->prepare($sql); 
        $resultSet =$stmt->executeQuery(); 
 
    // returns an array of arrays (i.e. a raw data set) 
    $listaProductos=$resultSet->fetchAllAssociative(); 
  
     return $listaProductos;  ...

Cómo escribir consultas SQL y el tratamiento de los resultados

Podemos asociar a una consulta SQL un objeto de tipo ResultSetMapping para mapear el conjunto de valores en un objeto.

Los valores de la consulta se traducen en propiedades del objeto.

Simplemente, instanciamos un objeto $rsm:

$rsm = new ResultSetMappingBuilder($this->getEntityManager()); 

y a continuación le asociamos una clase:

    $rsm->addEntityResult(MyClass::class, "m"); 

Es necesario mapear el nombre de cada columna en la base de datos a las propiedades de nuestras entidades:

foreach ($this->getClassMetadata()->fieldMappings as $miObjeto) { 
        $rsm->addFieldResult("m", $miObjeto["columnName"], 
$miObjeto["fieldName"]); 
    } 

Finalmente, ejecutamos nuestra consulta SQL pasando el objeto $rsm:

$stmt = $this->getEntityManager()->createNativeQuery($sql, $rsm); 

Las relaciones entre entidades

Con DQL o Query Builder, podemos realizar uniones entre diferentes tablas.

A menudo, no toda la información está en una sola tabla, sino distribuida en varias tablas (por ejemplo, puede haber una unión entre productos y sus marcas).

Las tablas utilizan campos (llamados claves foráneas) o tablas intermedias para realizar sus uniones.

Aquí veremos cómo podemos hacer uniones directamente en las entidades, sin pasar por DQL o Query Builder.

Hay cuatro tipos de relaciones. Utilizaremos la nomenclatura en inglés.

OneToOne

Un registro de la tabla principal (que llamamos tabla propietaria) solo puede estar vinculado a un único registro de la tabla secundaria (que llamamos tabla inversa) y, recíprocamente, un registro de la tabla inversa solo puede estar vinculado a un registro de la tabla propietaria.

Ejemplo

Imaginemos una tabla Referencia que contiene el número de referencia de cada producto. La relación entre las dos tablas es del tipo OneToOne. Es una relación de unicidad.

OneToMany

Un registro de la tabla propietaria puede estar vinculado a varios registros de la tabla inversa, pero un registro de la tabla inversa solo puede estar vinculado a un registro de la tabla propietaria.

Ejemplo

Imaginemos una tabla Alias que agrupa para cada producto, los alias del nombre del producto posibles (por ejemplo, para ordenador: PC, microordenador, ordenador...). La relación...

Las relaciones OneToOne

Veamos el ejemplo de una tabla Referencia vinculada a nuestra tabla producto.

Crearemos la tabla Referencia que contendrá un solo campo: el número.

php bin/console make:entity 

Hay que responder a las preguntas de la siguiente manera:

Class name of the entity to create or update (e.g. FierceElephant): 
> Referencia 
Add the ability to broadcast entity updates using Symfony UX Turbo? 
(yes/no) [no]: 
created: src/Entity/Referencia.php  
created: src/Repository/ReferenciaRepository.php  
  
Entity generated! Now let's add some fields!  
You can always add more fields later manually or by re-running this 
command. 
New property name (press <return> to stop adding fields): 
> numero 
Field type (enter ? to see all types) [string]: 
> integer 
Can this field be null in the database (nullable) (yes/no) [no]: 
> no 
Add another property? Enter the property name (or press <return> to stop 
adding fields): 
>  
  Success! 

Hacemos la migración:

php bin/console make:migration 

y actualizamos la base de datos:

php bin/console doctrine:migrations:migrate 

La tabla Referencia ahora está creada. Crearemos una unión OneToOne entre la entidad Producto y la entidad Referencia.

Todo se realiza en la entidad propietaria, que en este caso es la entidad Producto.

La relación OneToOne se realizará agregando una nueva propiedad que hará la unión. Llamemos a esta propiedad $referencia.

Las anotaciones indicarán a Doctrine que esta propiedad es una propiedad de unión OneToOne.

En la entidad Producto, simplemente agregamos:

#[ORM\OneToOne(targetEntity:Referencia::class,cascade:["persist"])] 
private $referencia = null; 

targetEntity indica el nombre de la entidad inversa Referencia.

cascade:["persist"] indica que no será necesario persistir los objetos de la entidad Referencia que se unirán a la entidad principal (volveremos sobre esto más adelante).

La propiedad que escribimos a mano no tiene métodos de acceso (getReferencia() y setReferencia($referencia)), ya que la creamos manualmente.

Podemos generarlos automáticamente con el siguiente comando en el terminal:

 php bin/console make:entity --regenerate App 

Este comando actualiza todas las entidades presentes...

Las relaciones ManyToMany

Vamos a trabajar el caso en el que podemos tener tantas relaciones como deseemos entre la tabla propietaria y la tabla inversa, y viceversa.

Esta vez, una columna de unión, como hemos visto con anterioridad, no es suficiente. Para este tipo de relación, es necesario crear una tabla de unión intermedia entre las dos tablas.

No se preocupe, Doctrine se encarga de todo.

Vamos a crear una nueva entidad Distribuidor para hacer nuestra unión.

Distribuidor contendrá simplemente una propiedad $nombre.

php bin/console make:entity 

Debe responder a las preguntas:

Class name of the entity to create or update (e.g. DeliciousPizza): 
> Distribuidor  
Add the ability to broadcast entity updates using Symfony UX Turbo? 
(yes/no) [no]:  
> 

New property name : nombre 
type : string 
length : 255 

Hacemos la migration:

php bin/console make:migration 

y actualizamos la base de datos:

php bin/console doctrine:migrations:migrate 

La relación ManyToMany entre la entidad Producto y la entidad Distribuidor se hace de manera similar a la relación OneToOne, gracias a las anotaciones.

En la entidad Producto, agregue esta propiedad:

#[ORM\ManyToMany(targetEntity:Distribuidor::class, cascade:["persist"])] 
private $distribuidores = null; 

Escribimos intencionalmente la propiedad $distribuidores en plural. De hecho, esta debería recibir varias entidades de la clase Distribuidor, ya que es una relación múltiple.

Esta propiedad será de tipo ArrayCollection.

Debe generar los accesors:

 php bin/console make:entity --regenerate App 

Si observa los métodos de acceso creados en la entidad Producto, notará que no son los mismos que para la relación OneToOne.

Encontrará un método addDistribuidor, que permitirá agregar un distribuidor, y un método removeDistribuidor para eliminarlo.

Ahora debe generar las migraciones para actualizar la base de datos:

php bin/console make:migration  
php bin/console doctrine:migrations:migrate 

Al ir a localhost/phpmyadmin, notará que se han creado dos tablas: una tabla de distribuidor y una tabla de producto_distribuidor, que gestionará las uniones múltiples.

Vamos a crear la fixture que llenará la tabla de distribuidor y realizará múltiples uniones con la tabla de producto.

Cree el archivo...

Las relaciones bidireccionales

Por ahora, siempre hemos definido en nuestras uniones una tabla prioritaria y una tabla inversa, lo que significa que no hay reciprocidad.

En la unión, siempre partimos de una tabla (la tabla prioritaria) para buscar información en la tabla inversa.

¿Qué pasa si queremos hacer lo contrario?

Por ejemplo: con la unión ManyToMany anterior, queremos crear una vista que liste las marcas de los distribuidores y que muestre, para cada uno de ellos, los números de los productos que distribuyen.

Es imposible hacer en el estado actual, porque no tenemos una propiedad $producto en la entidad Distribuidor. Si agregamos esta propiedad y le asociamos una unión ManyToMany, es poco probable que la tabla de unión intermedia sea la misma (tendríamos una segunda tabla de unión: distribuidor_producto, lo que no es fácil de manejar).

Por lo tanto, vamos a establecer una relación bidireccional.

El primer paso es agregar la propiedad $producto de unión en la tabla inversa, es decir, la tabla distribuidor.

Le asociamos una unión ManyToMany, pero teniendo cuidado de especificar que es una relación inversa con la propiedad mappedBy.

Preste atención: esta opción toma como valor el nombre de la propiedad que sirve como unión en la tabla propietaria. Para nuestro ejemplo, el nombre de esta propiedad es, si miramos la entidad Producto: $distribuidores.

Por lo tanto, esto es lo que debemos agregar en la entidad Distribuidor:

#[ORM\ManyToMany(targetEntity:Producto::class,  
mappedBy:'distribuidores')]  
private $productos = null; 

¡Pero eso no es todo! Necesitamos decirle a la entidad Producto que hay una relación bidireccional.

Para ello, basta con agregar la opción inversedBy...

Las relaciones bidireccionales con atributos

Imaginemos que queremos conocer el número de productos generados para cada producto por marca.

Esto significa que, en cada unión entre la entidad Producto y la entidad Distribuidor, tendríamos que especificar un parámetro numProducto.

Pero ¿dónde almacenar esta información?

No es posible en la entidad Producto porque esta información depende de cada marca. Lo mismo ocurre con la entidad Distribuidor: la información depende del producto.

Deberíamos poder insertarlo en la tabla de unión producto_distribuidor, pero no tenemos acceso a esa tabla. Symfony la administra por nosotros.

La única solución es crear y administrar nosotros mismos la tabla intermedia para poder agregar la columna numProducto. Pero ya no deberíamos pasar por una unión ManyToMany.

Deberemos manejar una relación OneToMany entre la entidad producto y la entidad intermedia que crearemos y una relación ManyToOne entre la entidad intermedia y la tabla distribuidor.

El Lazy Loading

Es muy importante tener presente este concepto cuando se desarrolla con uniones.

Symfony utiliza un proceso llamado Lazy Loading para realizar sus uniones. Intenta optimizar las consultas. En resumen, carga solo lo que necesita en el momento en que lo necesita.

Así que, cuando ejecuta el comando:

$listaProductos=$productosRepository->findAll(); 

la consulta generada no realiza una unión real. Utiliza un objeto Proxy para simular la unión. Mientras no se solicite información de la tabla inversa, la consulta no realiza la unión.

Para obtener más detalles sobre los objetos Proxy, consulte el enlace: https://es.wikipedia.org/wiki/Servidor_proxy

Veamos un ejemplo.

Creemos una acción eager() que llame a una vista para mostrar los números de los productos:

#[Route("/eager",name: "eager")]  
  public function eager(EntityManagerInterface $entityManager) 
    {  
    $productosRepository=$entityManager->getRepository(Producto::class); 
    $listaProductos=$productosRepository->findAll();  
        return $this->render('lista_productos/eager.html.twig', [  
            'listaProductos' => $listaProductos,  
  
        ]);  ...

La ingeniería inversa

Es posible que comience Symfony con una base de datos ya existente. Esto sucede a menudo en las empresas. Si su base de datos contiene muchas tablas, será bastante tedioso crear manualmente todas las entidades correspondientes en su aplicación.

Symfony le brinda la posibilidad de crear automáticamente todas las entidades a partir de la base de datos.

En el terminal, ejecute el comando:

php bin/console doctrine:mapping:import "App\Entity" annotation 
--path=src/Entity/Reverse 

En la carpeta src/Entity, encontrará una carpeta Reverse que contendrá todas las entidades generadas.

Si abre la entidad Producto, verá que se han detectado las uniones. Hay un ManyToMany en la propiedad $distribuidor.

Sin embargo, Symfony ha realizado una unión ManyToOne (por defecto) en la propiedad $referencia. De hecho, no hay nada que diferencie un ManyToOne de un OneToOne en la estructura de las tablas.

Le corresponde a usted aplicar las correcciones manualmente.

También necesita generar los métodos de acceso con el comando:

php bin/console make:entity --regenerate App 

Advertencia: elimine la carpeta Reverse y su contenido para continuar. Si no lo hace, tendrá conflictos entre las entidades de la carpeta Reverse y las entidades del mismo nombre que creamos en la carpeta Entity.