This course is still being released! Check back later for more chapters.
Herencia de tablas de clases
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeEs hora de sumergirnos en el último tipo de Herencia Doctrine: la Herencia de Tabla de Clase. Ésta vuelve a utilizar una única tabla por entidad en la jerarquía. Pero aquí está la diferencia: en la tabla sólo se almacenan las propiedades específicas de esa entidad (más el id). Al buscar, Doctrine realiza las uniones necesarias entre bastidores para obtener todos los datos de la entidad que estás buscando.
En Starship, en el atributo InheritanceType, cambia SINGLE_TABLEpor JOINED:
| // ... lines 1 - 8 | |
| #[ORM\InheritanceType('JOINED')] | |
| // ... lines 10 - 14 | |
| abstract class Starship | |
| // ... lines 16 - 116 |
Doctrine utilizaba tradicionalmente JOINED para referirse a este tipo de herencia, pero se conoce más comúnmente como Herencia de tabla de clases. ¡Ya está!
Note
Aunque este cambio de código es superfácil, los cambios en la base de datos no lo son. Si tuvieras una base de datos ya establecida con datos en ella, tendrías que hacer algunas migraciones cuidadosas.
Para mostrar mejor cómo cambia el SQL, eliminemos el esquema y empecemos de cero. En el terminal, ejecuta
symfony console doctrine:schema:drop --force
Ahora, crea el esquema con:
symfony console doctrine:schema:create --dump-sql
Observar los cambios
Veamos lo que tenemos aquí. La tabla del carguero sólo tiene id y cargo_capacity. La tabla starship tiene todas las propiedades comunes, y la tabla scout sólo tiene idy sensor_range.
Carguemos nuestros accesorios para asegurarnos de que todo sigue funcionando.
symfony console foundry:load-fixtures
Se ve bien. Salta a la aplicación y actualiza. ¡Perfecto! ¡Seguimos teniendo seis naves!
Comprobación de la base de datos
Ahora, echemos un vistazo a la base de datos para ver con qué estamos tratando. De nuevo en el terminal, ejecuta:
symfony console doctrine:query:sql 'select * from starship'
Note
Recuerda que este comando ha cambiado a dbal:run-sql en las nuevas versiones de Doctrine.
Como puedes ver, sólo contiene las columnas de la entidad Starship, no las deFreighter y Scout. Sin embargo, sigue incluyendo una ship_type. Y sí, tenemos tres cargueros y tres exploradores. Si queremos ver las propiedades específicas de éstos, están en sus respectivas tablas.
Veamos la tabla de los cargueros. En primer lugar, fíjate en la columna id para los cargueros, 1, 2 y 3. Deberían coincidir con los identificadores de la tabla de cargueros. Ejecuta:
symfony console doctrine:query:sql 'select * from freighter'
Esto sólo tiene los cargo_capacity's de los tres cargueros, y efectivamente, los ids coinciden con los ids de la tabla de naves estelares. Así, cuando Doctrine construye (o hidrata) una nave estelar, sabe que la nave con id 1 es un carguero. Entonces instanciará un objeto Freighter, tomará las propiedades comunes de la tabla de naves estelares, se unirá con la tabla de cargueros en el id y tomará la capacidad de carga. Uf, ¡me alegro de que Doctrine haga todo eso por nosotros!
Inspección del perfilador
Volviendo a la aplicación, dediquemos un momento a comprobar el perfilador y ver esto en acción. Haz clic en la consulta formateada para verla mejor. La consulta es un poco más compleja que antes, debido a las uniones, pero hace exactamente lo que esperábamos.
Añadir una nueva entidad
Vamos a subir la apuesta con un escenario un poco más avanzado. Vamos a crear otra nave: un carguero minero. Será una subclase de Freighter, lo que nos llevará a otro nivel de profundidad en el árbol de herencia.
De vuelta al terminal, crea la entidad con:
symfony console make:entity MiningFreighter
Dale una propiedad llamada laserPower, un número entero, no anulable, y listo. Ahora la fábrica de la Fundición:
symfony console make:factory
Elige all para crear el MiningFreighter que falta.
De vuelta en nuestro IDE, abre nuestra nueva entidad MiningFreighter. Haz que extienda Freighter y elimina la propiedad id y el getter:
| // ... lines 1 - 7 | |
| #[ORM\Entity(repositoryClass: MiningFreighterRepository::class)] | |
| class MiningFreighter extends Freighter | |
| // ... lines 10 - 26 |
Actualizar la fábrica
Ahora, abre el MiningFreighterFactory. Tenemos que hacer algunos ajustes en el FreighterFactory antes de poder extenderlo, así que ábrelo.
Elimina el final para que podamos ampliarlo. A continuación, tenemos que añadir una plantilla para que las subfábricas puedan especificar el tipo de entidad que están creando. En el docblock de la clase, añade@template T of Freighter. Luego, en el @extends, amplíalo a T:
| // ... lines 1 - 6 | |
| /** | |
| * @template T of Freighter | |
| * @extends StarshipFactory<T> | |
| */ | |
| class FreighterFactory extends StarshipFactory | |
| // ... lines 12 - 27 |
De vuelta en MiningFreighterFactory, haz que la clase extienda a FreighterFactory. Haz lo mismo para @extends en el docblock:
| // ... lines 1 - 7 | |
| /** | |
| * @extends FreighterFactory<MiningFreighter> | |
| */ | |
| final class MiningFreighterFactory extends FreighterFactory | |
| // ... lines 12 - 27 |
Abajo en defatuls(), añade nuestro truco array_merge. array_merge(parent::defaults(), el segundo argumento, la matriz, y no olvides cerrar la función:
| // ... lines 1 - 10 | |
| final class MiningFreighterFactory extends FreighterFactory | |
| { | |
| // ... lines 13 - 18 | |
| protected function defaults(): array | |
| { | |
| return array_merge(parent::defaults(), [ | |
| 'laserPower' => self::faker()->randomNumber(), | |
| ]); | |
| } | |
| } |
Aquí tenemos un inicio genial de array_merge. El MiningFreighterFactory se está fusionando con los valores por defecto del FreighterFactory, que se está fusionando con los valores por defecto del StarshipFactory.
Crear algunos datos
A continuación, en AppStory, crea unos cuantos cargueros mineros con MiningFreighterFactory::createMany(2):
| // ... lines 1 - 11 | |
| final class AppStory extends Story | |
| { | |
| public function build(): void | |
| { | |
| // ... lines 16 - 17 | |
| MiningFreighterFactory::createMany(2); | |
| } | |
| } |
De vuelta en el terminal, vuelve a cargar los accesorios.
symfony console foundry:load-fixtures
Uy, tenemos un error... "La entidad MiningFreighter tiene que formar parte del mapa discriminador de Starship para estar correctamente mapeada en la jerarquía de herencia"
Este es un paso habitual que se olvida al añadir nuevas entidades a una jerarquía de herencia.
De vuelta en Starship, en la matriz DiscriminatorMap, añade nuestra nueva entidad con'mining_freighter' => MiningFreighter::class:
| // ... lines 1 - 10 | |
| #[ORM\DiscriminatorMap([ | |
| // ... lines 12 - 13 | |
| 'mining_freighter' => MiningFreighter::class, | |
| ])] | |
| abstract class Starship | |
| // ... lines 17 - 117 |
Me gusta utilizar snake case para las claves, pero puedes utilizar lo que quieras. Lo importante es que el valor sea el nombre de la clase de la entidad.
Ahora carga de nuevo nuestros accesorios.
symfony console foundry:load-fixtures
Bien, ¡ha funcionado! Actualiza nuestra aplicación... ¡y voilá! ¡Ya tenemos ocho naves!
Conclusión
Vuelve a echar un vistazo a la consulta en el perfilador. Se ha añadido otro join para la tabla mining_freighter.
Este es uno de los contras de la herencia de tablas de clases, las consultas se vuelven más complejas cuantas más entidades tengas en tu jerarquía. Esto puede reducir el rendimiento.
Otro contra en el que quizá no pienses es que, si utilizas herramientas externas para consultar y manipular tu base de datos, es más difícil trabajar con ella. Tienes que duplicar las uniones y la lógica que Doctrine utiliza internamente.
Como en la mayoría de las cosas, hay ventajas y desventajas con cada tipo de herencia.
A continuación, veremos cómo consultar los distintos tipos de naves estelares.
Comments
"Houston: no signs of life"
Start the conversation!