Acceder a las bases de datos con Doctrine
Presentación y conceptos
1. Aspectos básicos de ORM
Doctrine es una librería que permite administrar las interacciones entre una aplicación y una (o más) bases de datos. Aunque esta no es la única forma de comunicarse con una base de datos (hay otras librerías, como Propel, la extensión PHP PDO, etc.), Symfony claramente ha hecho de Doctrine su opción favorita. De hecho, esta última está preconfigurada por defecto en Symfony desde la versión 2 del framework.
ORM (Object-To-Relational Mapping), o mapping de objetos relacionales, es un concepto que permite asociar clases del modelo de objetos de PHP con tablas de bases de datos. Una vez que se realiza esta asociación (el «mapping»), la manipulación de clases y objetos actúa directamente sobre los datos almacenados en la base de datos, lo que permite al programador simplificar las operaciones básicas de gestión de datos.
2. Arquitectura de Doctrine
Antes de llegar al núcleo del asunto, es necesario definir ciertos conceptos necesarios para comprender el funcionamiento de Doctrine.
Doctrine se compone de varias capas: DBAL, ORM y Entidad.
a. DBAL
La capa DBAL (Database Abstraction Layer) es la de nivel más bajo. No tiene ninguna lógica aplicativa y su función es enviar consultas a una base de datos y recuperar los resultados. Es comparable a la extensión...
Instalar y configurar Doctrine
1. Configurar Doctrine
Si ha creado la aplicación utilizando el esqueleto de la aplicación web mediante uno de los siguientes comandos:
composer create-project symfony/website-skeleton mi_proyecto 5.4.*
o
symfony new mi_proyecto --version=5.4 --webapp
entonces Doctrine ya está presente de forma predeterminada en su aplicación, como demuestra el archivo composer.json ubicado en la raíz del proyecto:
{
"type": "project",
"license": "proprietary",
"require": {
"php": ">=7.2.5",
...
"doctrine/doctrine-bundle": "^2.7",
...
},
...
}
De lo contrario, es necesario instalar el paquete Symfony ORM a través del comando:
composer requiere symfony/orm-pack
2. Relación con PDO
Como vimos anteriormente, la capa DBAL de Doctrine es directamente responsable de interactuar con la base de datos. Para ello, Doctrine utiliza la librería PDO (PHP Data Object), disponible en el lenguaje PHP desde la versión 5.3 de esta última. PDO permite utilizar un código de acceso a los datos idéntico, independientemente...
Definir entidades y su mapeo
El primer paso consiste en crear las entidades. Como hemos visto anteriormente, una entidad es una clase «especial», en el sentido de que Doctrine es capaz de asociarla con una tabla de base de datos. Este concepto puede no quedar claro al principio, así que vamos a ilustrarlo con un ejemplo.
1. Reglas de diseño de las entidades
Supongamos que desea listar los libros en una base de datos y cada libro tiene un id, un título y una fecha de lanzamiento.
Aquí la entidad es una sencilla clase, que contiene la diferente información en propiedades:
<?php
namespace App\Entity;
class Libro
{
private $id;
private $titulo;
private $fechaAparicion;
/**
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* @param string $titulo
* @return Libro
*/
public function setTitulo($titulo)
{
$this->titulo = $titulo;
return $this;
}
/**
* @return string
*/
public function getTitulo()
{
return $this->titulo;
}
/**
* @param \DateTime $fechaAparicion
* @return Libro
*/
public function setFechaAparicion($fechaAparicion)
{
$this->fechaAparicion = $fechaAparicion;
return $this;
}
/**
* @return \DateTime
*/
public function getFechaAparicion()
{
return $this->fechaAparicion;
}
}
Las propiedades...
Manipular entidades con EntityManager
1. El rol de EntityManager
EntityManager es el objeto que permite realizar operaciones relacionadas con la modificación de los datos, a saber: consultas de tipo INSERT, UPDATE y DELETE.
A nivel de su aplicación, incluso si manipula entidades, que se corresponden con los datos presentes en la base de datos, no es suficiente con modificar el valor de una propiedad (a través de un mutador) para que Doctrine haga la sincronización en la base de datos. Debe administrar estas operaciones con EntityManager.
2. Insertar datos
Para insertar una fila en una tabla determinada, primero debe instanciar la entidad correspondiente a esa tabla:
use App\Entity\Libro;
$libro = new Libro();
Seguidamente, los datos que se han de insertar se deben inyectar a través de los mutadores:
use App\Entity\Libro;
$libro = new Libro();
$libro->setTitulo('La granja de los animales');
$libro->setFechaAparicion(new \DateTime('1945-08-17'));
Para terminar, solo falta pedir a Doctrine que envíe la consulta de inserción a la base de datos:
use App\Entity\Libro;
$libro = new Libro();
$libro->setTitulo('La granja de los animales');
$libro->setFechaAparicion(new \DateTime('1945-08-17'));
$em = $container->get('doctrine')->getManager();
$em->persist($libro);
$em->flush();
Volveremos a este último paso con más detalle. En primer lugar, vamos a recuperar el EntityManager de Doctrine. Este último se puede recuperar invocando el método getManager() del servicio doctrine.
En la mayoría de las situaciones, el código de recuperación de EntityManager está dentro de un servicio de manipulación de datos creado en su aplicación. La ventaja de este enfoque es que se puede inyectar EntityManager Doctrine. He aquí un ejemplo de un servicio como este:
namespace App\Model;
use App\Entity\Libro;
use Doctrine\ORM\EntityManagerInterface;
class ArticuloServicio
{
private $em;
public function __construct(EntityManagerInterface $em)
{ ...
Recuperar entidades
1. El repositorio
a. Un rol centralizador
El repository es donde se agrupan las consultas relativas a una entidad. La mayoría está relacionada con la recuperación de datos (SELECT), pero el repositorio también puede contener operaciones de creación, modificación o eliminación.
Cada entidad tiene su propio repositorio. Se puede materializar por una clase definida por el desarrollador si se rellena la propiedad repositoryClass de la anotación @Entity o, llegado el caso, mediante una clase interna de Doctrine.
Hemos utilizado un repositorio en un ejemplo anterior (modificación de una entidad). Lo usamos para recuperar una entidad en función del valor de su clave primaria:
$repository = $this->em->getRepository(App:Libro');
$libro = $repository->find(1);
Vamos a desglosar el código anterior en varias partes distintas.
Recuperar el repositorio
Para recuperar el repositorio de una entidad determinada, debe invocar el método getRepository() del EntityManager. Como parámetro, recibe el FQCN (Fully Qualified Class Name) de la entidad cuyo repositorio se desea.
Como habrá observado, App:Libro no es un nombre de clase, sino un acceso directo específico de Symfony que se corresponde con App\Entity\Libro. Ambos son totalmente válidos y se pueden utilizar, aunque el primero tiene la ventaja de ser más corto.
Recuperar una entidad
En un segundo paso, recuperamos la entidad cuya clave primaria es 1 invocando el método find() en el repositorio.
b. Los métodos básicos del repositorio
El método find() que acabamos de usar forma parte de algunos métodos definidos por defecto en un repositorio. Estos métodos básicos permiten recuperar una o más entidades en función del valor de una o varias columnas, y tienen la particularidad de que todos comienzan por find.
findAll()
Este método devuelve todas las entidades persistidas en la base de datos. Por lo tanto, el resultado es una tabla de entidades.
find()
El método find() recupera una entidad en función del valor de su clave primaria:
// recupera la entidad cuyo identificador es 15
$entidad = $repository->find(15);
Si la entidad tiene una clave primaria que contiene varias columnas, debe usar el método find() de la siguiente...
Funciones avanzadas
1. Las extensiones Doctrine
Las extensiones de Doctrine son una funcionalidad imprescindible. Resultan muy prácticas y permiten añadir ciertos comportamientos a sus entidades, como por ejemplo:
-
un slug: una propiedad generada de forma automática que contiene un identificador legible para su entidad, perfectamente adaptado a las URL (ilustraremos esta extensión con un ejemplo),
-
las traducciones: el contenido de sus entidades se puede traducir a varios idiomas,
-
la adición de propiedades created y updated: fechas que se actualizan automáticamente durante la creación y la última modificación de la entidad.
Esta lista no es exhaustiva, hay muchas otras extensiones.
a. Instalación
Estas extensiones no se instalan de forma predeterminada. El bundle DoctrineExtensionsBundle los pone a su disposición.
Para instalarlo, es suficiente con utilizar la recipe Symfony asociada y ejecutar el comando:
composer require antishov/doctrine-extensions-bundle
b. Utilizar un slug en una entidad
El bundle está listo para utilizarse. Configuremos la extensión sluggable (para la generación de slugg) en nuestra entidad Libro.
El slug es una propiedad especial que nos permitirá usar URL adaptadas al SEO, con texto en lugar de un identificador.
Primero, debe activar la extensión a nivel de configuración del bundle:
# config/packages/stof_doctrine_extensions.yaml ...