Diseñando BDs con el creador de entidades de Symfony para Doctrine

Trabajando las entidades de Doctrine con el maker de Symfony..

Un lector me ha escrito sobre un tema muy interesante de Symfony. Así que aquí estoy de nuevo añadiendo material académico. Este post va sobre un post anterior, sobre Symfony, los modelos y entidades, de la serie de tutoriales de iniciación a Symfony Flex. Voy a tratar de explicarme mejor sobre el día a día con los comandos de consola mientras que vamos creando o editando una base de datos.

Symfony te convierte en un programador web muy productivo, gracias en gran medida a los comandos de consola. Así que es muy recomendable conocerlos bien, practicar mucho, y saber usarlos. Es muy recomendable para sacarle provecho a este post el tener los conocimientos teóricos sobre diseño de bases de datos. Así pasarás del esquema de la base de datos, a crearla, en muy poco tiempo gracias al maker de entidades de Symfony. Pero es necesario que tengas claro cómo establecer las relaciones entre entidades, y de qué forma deben ser: de una a una, de una a muchas o de muchas a muchas.

En concreto para trabajar la persistencia de los datos en bases de datos, tenemos un maker de entidades, además de una serie de comandos propios de Doctrine. Para los que llegan a este post sin pasar por lo anteriores, tengo que decir que Doctrine es un ORM, que te independiza de la BD, y te facilita mucho el día a día. ORM significa Object Relational Mapping, que resumiendo, te genera una serie de objetos en PHP en este caso, con los que puedes trabajar el acceso a la información que se almacena en la base de datos.

Al grano, un proyecto de pruebas

Para jugar con el maker de entidades podemos crear un proyecto desastre, y así lo vamos viendo paso a paso. Creando el proyecto:

composer create-project symfony/skeleton symfony-entity-maker
cd symfony-entity-maker
composer require --dev maker
composer require doctrine annotations

Lo siguiente es configurar el fichero .env del proyecto, en mi caso, para desarrollo local, configuro la siguiente línea:

DATABASE_URL=mysql://root:contraseña@127.0.0.1:3306/symfony_entity_maker

..y ya podemos ejecutar la creación de la base de datos:

php bin/console doctrine:database:create

Especificaciones, cómo diseñar una base de datos

El primer paso, partiendo de la especificación con el cliente, es diseñar la base de datos. Aquí es donde hablando con el cliente o las personas que van a usar la aplicación, tendremos que detallar lo mejor posible qué información vamos a necesitar almacenar en la base de datos. Este paso es muy importante a la hora de arrancar un nuevo proyecto. Una buena especificación, nos ahorrará mucho tiempo de desarrollo después, y el usuario final, o cliente, estará más contento.

Un ejemplo de especificación para una base de datos:

Se quiere diseñar una base de datos para organizar los reportes de incidencias que lleva una empresa. De los reportes de incidencias queremos saber la fecha, trabajador o cliente que la ha reportado, asunto, texto explicativo, nivel de urgencia, estado, estimación de tiempo para resolverlo, y posición en la cola de tareas. De cada trabajador sólo queremos almacenar su nombre, apellidos y email. De cada cliente queremos almacenar además su dni, teléfono móvil, calle, localidad, región, país y unas observaciones. Además queremos saber si un trabajador ya está asignado a una incidencia para resolverla, un trabajador puede estar asignado a varias incidencias, y una incidencia puede tener varios trabajadores asignados. Para reducir el número de incidencias repetidas, se tiene que poder saber si varios clientes han reportado la misma incidencia.

Ya tenemos una buena especificación de una pequeña aplicación web. Queda cerrada la especificación, y nos ponemos manos a la obra con el diseño relacional. Aquí lo suyo es coger lápiz y papel y hacer el modelo Entidad Relación Extendido.

Diseñando la base de datos, el modelo Entidad Relación Extendido

Para este caso, claramente tenemos que ver que necesitamos estas entidades:

  • Incidencia
  • Trabajador
  • Cliente

Cliente y trabajador, como ambos tienen datos parecidos, podrían haberse puesto en una misma entidad. Pero por eficiencia de espacio en la base de datos, los he separado, ya que de trabajadores sólo queremos pocos datos y de clientes queremos almacenar más datos.

Tendremos entonces tres relaciones entre estas entidades:

  • Clientes que reportan incidencias.
  • Trabajadores que reportan incidencias.
  • Las asignaciones de trabajadores a incidencias.

Cómo diseñar una base de datos es materia de estudios universitarios. Se trata del diseño y la gestión de bases de datos. No puedo resumirlo aquí el porqué de estas relaciones, pero si estás interesado en saber más, recomiendo estudiarse el modelo EER (que significa entidad relación extendido), eficiencia, evitar duplicidades, integridad de los datos..

Normalmente, el crear las tablas de datos será bastante sencillo, pero un buen diseño relacional en este paso, te ahorrará muchos quebraderos de cabeza después. Un mal diseño de la BD puede ser el caos a la larga..

A saber, para este caso, las tres relaciones citadas serán:

  • Los clientes que reportan incidencias será una relación de muchos a muchos. Es así porque un cliente puede tener muchas incidencias reportadas. Y además, una incidencia puede ser reportada por varios clientes, y como hemos dicho, esto es así porque queremos evitar el que se reporte repetidas veces la misma incidencia, por varios clientes, como se cita en la especificación.
  • Para los trabajadores que reporten incidencias, es una relación de uno a muchos. Un trabajador puede reportar varias incidencias, pero una incidencia reportada por un trabajador, no debería ser reportada por más trabajadores. Supongo que según mi diseño, los trabajadores hablan entre sí, trabajando en equipo, y no van a reportar la misma incidencia varios trabajadores. Entonces se queda como uno a muchos.
  • Sobre las asignaciones de incidencias a trabajadores, tenemos una relación muchos a muchos también. Así una incidencia se puede asignar a varios trabajadores. Y un trabajador puede estar asignado en varias incidencias.

Luego veremos cómo se reflejan estas relaciones entre entidades. Primero vamos crear entonces las entidades independientes en un primer paso, y en un segundo las relaciones. Así se verá claro el proceso. Siempre se hace igual, cuando hayas diseñado 100-200 bases de datos verás cómo siempre es la misma historia, pero hay que practicar.. 😉

Manos a la obra, creando las entidades

Primero que todo, vamos a crear las entidades desde línea de comandos, sin tener en cuenta las relaciones. Empezamos por la entidad trabajador, así desde línea de comandos comenzamos con ‘php bin/console make:entity’ para la entidad llamada Worker que significa Trabajador:

..creado el fichero principal. Ahora vamos con los atributos..

..ya tenemos el nombre de tipo cadena de 255 caracteres, permite valor nulo.

Si le ponemos ? cuando nos pregunta por el tipo de dato, tendremos según la instalación, una pantalla como la siguiente con todos los tipos de datos disponibles hasta el momento:

Continuamos entonces creando los atributos para los trabajadores..

..ahora tenemos el campo para los apellidos..

..ahora el email, y en la última pregunta lo dejo en blanco para terminar la creción/edición de la entidad Worker..

Para crear la entidad cliente el proceso es muy parecido. Cliente en inglés es Customer, así que siguiendo las buenas prácticas, todo en inglés para programar.. comenzamos haciendo así:

..fichero inicial con su repositorio de consultas creado..

..ahora el nombre, igual que para los trabajadores..

..también tiene apellidos..

Ahora he terminado la edición de cliente, se ha creado el fichero, y simulando que quiero seguir editando la entidad, podemos lanzar la creación de nuevo. Si los ficheros PHP de la entidad y repositorio de consultas ya existen, el maker lo que hace es editar la entidad. No hay problema. Así que de este modo arranco la edición de Customer para añadir más atributos.

Lo que no podemos hacer es borrar atributos con el maker. Para borrarlos tenemos que editar nosotros mismos el PHP. Continuamos..

..ya le hemos añadido el dni..

..ya le hemos añadido un campo para el número de teléfono..

..ya le hemos añadido la calle, también de tipo cadena de 255 caracteres..

..la localidad donde se encuentra la calle..

..ahora la región, que según el país, puede corresponder a la provincia o estado o región, según se nombre en cada país..

..ahora el país..

..y unas observaciones. Para terminar entonces le decimos que ya lo tenemos dejando en blanco la última pregunta..

..y finaliza el proceso.

Ahora vamos con los reportes de incidencias, que para crearlo en inglés podemos ponerle Report, o IncidenceReport:

..ya tenemos los reportes con su fecha de creación..

..el asunto..

..un texto descriptipo que es de tipo de campo text. Los campos de tipo text admiten muchos párrafos, contenidos muy largos. Dependerá de cada base de datos que utilicemos para establecer un largo máximo.

..ahora tenemos creado el nivel de urgencia de tipo entero..

..el estado de la incidencia, de tipo cadena de 8 caracteres, para poder poner una palabra que lo describa a nuestro gusto..

..ahora tenemos una estimación de horas..

..y la posición en la cola de tareas, por si queremos organizar el orden con algún algoritmo automático o manualmente más adelante..

..finalmente hemos terminado y Symfony nos invita a hacer una migración. Cómo hacer migraciones de la BD es material para otro post. Lo correcto es hacer una migración, pero si recién estamos arrancando el proyecto y sólo lo tenemos en local, te recomiendo que sólo hagas actualizaciones de la BD con el comando siguiente:

php bin/console doctrine:schema:update --dump-sql --force

Cuando ya tengas la BD en producción, y le hagas modificaciones, lo correcto es hacer migraciones, nunca el comando anterior. El comando anterior sólo te lo recomiendo para arrancar proyectos más rápido, si estás sólo en los proyectos, sabes lo que haces, son proyectos con pocos datos, y con bases de datos pequeñas.

Seguimos manos a la obra, creando las relaciones

Ahora viene lo que comentaba anteriormente, vamos con las 3 relaciones entre estas entidades. Ahora da igual desde qué relación lances el maker de Symfony, te guiará a través del proceso.

Para empezar con la relación de trabajadores asignados a reportes de incidencias. Voy a crear una función assignedWorkers en la entidad Report, que referencia a Worker así:

..y ahora en la dirección contraria, voy a crear una función llamada assignedReports en la entidad Worker. Así cuando tengamos cargado en PHP un trabajador, usando la función ->getAssignedReports() obtendremos todos los reportes que tiene asignados. Procedemos así para este caso:

Ahora seguimos con la relación que representa los trabajadores que reportan incidencias. Recordemos que esta es una relación una a muchos, es decir, un trabajador reporta muchas incidencias, pero cada incidencia reportada por un trabajador, sólo es reportada por un trabajador.

Primero empezamos por ejemplo desde la entidad Report, y vamos a nombrar a la relación de Report a Worker que representa que los trabajadores reportan indicencias, de nombre reportedByWorker. Así de esta forma, estando en un reporte de incidencia, si ha sido reportado por un trabajador podremos hacer lo siguiente:

$worker = $report->getReportedByWorker(); 

Entonces para hacer esto podemos hacerlo de esta forma:

..si seguimos con la última pregunta de la imagen, podemos hacer la relación contraria. Así estando en un trabajador, podremos saber qué incidencias ha reportado haciendo lo siguiente:

$reports = $worker->getReportedByReports();

Esto se hace como se ve en la imagen siguiente:

La última relación entre reportes de incidencias y clientes es una relación muchos a muchos. Así de igual forma, si estamos en un reporte le llamaremos por ejemplo ->getCustomers() para saber qué clientes han reportado. Y en dirección contraria, si estamos en un cliente, queremos hacer ->getReports().

Vamos a arrancar esta edición de entidades desde el cliente, por cambiar. Así podemos hacerlo editando con el maker la entidad Customer:

Si continuamos con la última pregunta de la imagen, hacemos entonces la relación inversa..

Ya están todas las entidades con atributos y relaciones mapeadas en los códigos fuentes en PHP. Vamos ahora con la actualización rápida a la BD.

Actualizando últimos cambios en la BD

Como he indicado antes, mientras que estamos arrancando un proyecto. Puede que estemos creando y borrando la BD. No veo muy necesario el ir haciendo migraciones así que si ejecutamos lo siguiente podremos actualizar la BD de una tacada:

php bin/console doctrine:schema:update --dump-sql --force

 The following SQL statements will be executed:

     CREATE TABLE report (id INT AUTO_INCREMENT NOT NULL, reported_by_worker_id INT DEFAULT NULL, created_at DATETIME NOT NULL, subject VARCHAR(255) DEFAULT NULL, text LONGTEXT DEFAULT NULL, urgency_level INT DEFAULT NULL, status VARCHAR(8) DEFAULT NULL, hours_estimation INT DEFAULT NULL, qeue_position INT DEFAULT NULL, INDEX IDX_C42F7784152406B7 (reported_by_worker_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
     CREATE TABLE report_worker (report_id INT NOT NULL, worker_id INT NOT NULL, INDEX IDX_B6416AFD4BD2A4C0 (report_id), INDEX IDX_B6416AFD6B20BA36 (worker_id), PRIMARY KEY(report_id, worker_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
     CREATE TABLE customer (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) DEFAULT NULL, surname VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, dni VARCHAR(12) DEFAULT NULL, mobile_phone_number VARCHAR(14) DEFAULT NULL, street VARCHAR(255) DEFAULT NULL, region VARCHAR(16) DEFAULT NULL, country VARCHAR(16) DEFAULT NULL, observations LONGTEXT DEFAULT NULL, locality VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
     CREATE TABLE customer_report (customer_id INT NOT NULL, report_id INT NOT NULL, INDEX IDX_75EAABD59395C3F3 (customer_id), INDEX IDX_75EAABD54BD2A4C0 (report_id), PRIMARY KEY(customer_id, report_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
     CREATE TABLE worker (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) DEFAULT NULL, surnames VARCHAR(255) DEFAULT NULL, email VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB;
     ALTER TABLE report ADD CONSTRAINT FK_C42F7784152406B7 FOREIGN KEY (reported_by_worker_id) REFERENCES worker (id);
     ALTER TABLE report_worker ADD CONSTRAINT FK_B6416AFD4BD2A4C0 FOREIGN KEY (report_id) REFERENCES report (id) ON DELETE CASCADE;
     ALTER TABLE report_worker ADD CONSTRAINT FK_B6416AFD6B20BA36 FOREIGN KEY (worker_id) REFERENCES worker (id) ON DELETE CASCADE;
     ALTER TABLE customer_report ADD CONSTRAINT FK_75EAABD59395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id) ON DELETE CASCADE;
     ALTER TABLE customer_report ADD CONSTRAINT FK_75EAABD54BD2A4C0 FOREIGN KEY (report_id) REFERENCES report (id) ON DELETE CASCADE;

 Updating database schema...

     10 queries were executed

                                                                                                                        
 [OK] Database schema updated successfully!

Terminando, recapitulando, qué tenemos ahora creado

Para terminar no me queda más que remitirme a la documentación oficial, e invitarte a seguir con el resto de posts de este blog aquí. Ahora podremos obtener todos los clientes, trabajadores y reportes fácilmente desde un controlador por ejemplo así:

$entityManager = $this->getDoctrine()->getManager();

$customers = $entityManager->getRepository('App:Customer')->findAll();
$workers = $entityManager->getRepository('App:Worker')->findAll();
$reports = $entityManager->getRepository('App:Report')->findAll();

foreach ($workers as $worker) {
    $thisWorkerReportsReported = $worker->getReportedByWorker();
    $thisWorkerAssignedReports = $worker->getAssinedReports();
}

foreach ($customers as $customer) {
    $thisCustomer = $customer->getReports();
}

¿A que mola un pegote? No tenemos que ir haciendo complejas consultas a la BD para ir construyendo la aplicación, o por lo menos la mayor parte de las veces. Doctrine es uno de los mejores componentes que incorporar a los proyectos web.

Mira que he añadido dos bucles que recorren los trabajadores y clientes para trabajar sus reportes relacionados. Te invito a que mires los ficheros PHP generados en el directorio src/Entity/.. Ahí verás todo lo que ha generado el maker, y te podrás imaginar la de horas y horas de trabajo que te has ahorrado sin tener que picar todo ese código 😉

Si has llegado hasta aquí abajo espero no haberte aburrido y que te haya servido de ayuda.

¡Un saludo! Otro día más..

Compartir..

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *