Introduccion a Doctrine 2 ORM
-
Upload
juan-ramon-laguardia -
Category
Software
-
view
766 -
download
0
Transcript of Introduccion a Doctrine 2 ORM
Introducción a Doctrine 2 ORM
Por J.R.Laguardia
Introducción a Doctrine 2 ORM
Fundamentos de Doctrine
Que es Doctrine.
Componentes de Doctrine.
Instalando Doctrine.
Un proyecto de ejemplo.
Entidades y mapeado
Las entidades.
Ciclo de vida de una entidad.
Estados de una entidad.
Tipos de datos y mapeado.
Creando las estructuras de nuestro ejemplo.
Asociaciones entre entidades
Tipos de asociaciones y cardinalidad.
Parte propietaria y parte inversa.
Hidratación de datos.
Una extensión de Doctrine: Data Fixtures
Insertando datos en nuestro ejemplo.
Consultas en Doctrine
DQL, Querybuilder y SQL Nativo.
Repositorios por defecto y personalizados.
Optimizando nuestro ejemplo.
Introducción a Doctrine 2 ORM
Fundamentos de Doctrine
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineQue es Doctrine
Doctrine es un conjunto de librerías que proveen un sistema para la persistencia de datos en PHP.
Funciona como una capa de abstracción, situada entre nuestra aplicación y el SGDB.
Esta diseñada para ser compatible con los SGBD mas comunes, como MySQL, MSSQL, PostgreSQL, SQLite y Oracle.
Puede dar soporte, a través de ODM (Object Document Model) a SGBD NoSQL, como MongoDB, CouchDB, OrientDB…
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineQue es Doctrine
Ampliamente utilizado de forma integrada con otros frameworks PHP, como Symfony, Zend Framework, Codeigniter, etc, etc… aunque puede utilizarse sin ellos.
Doctrine maneja los datos como objetos PHP (Entidades), de forma similar a lo que hace Hibernate en JAVA con los POJO (Plain Old Java Object).
La abstracción de las Entidades permite reusar nuestro código a pesar de que cambiemos el SGBD de trabajo.
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineQue es Doctrine
Doctrine emplea internamente dos patrones de diseño importantes:
Introducción a Doctrine 2 ORM
Data Mapper
• En Doctrine, el objeto que implementa este patrón se denomina Entity Manager.
• Realiza las inserciones, actualizaciones y borrado en la BD de los datos de las entidades gestionadas.
• Informa (hidrata) los objetos en memoria con datos obtenidos de la BD.
Unit of Work
• Este patrón es el empleado por el Entity Manager para acceder a la BD de forma transaccional.
• Mantiene el estado de las entidades gestionadas por el Entity Manager.
Fundamentos de DoctrineComponentes de Doctrine
ORM
DBAL
Common
ORM. Mapeador Relacional de Objetos. Permite el acceso a las tablas de las bases de datos a través de un API orientado al objeto. Construido sobre DBAL.
DBAL. Capa de Abstracción de Base de Datos. Provee de un interfaz común de acceso a los diferentes SGBD. Es similar a PDO, construida sobre ella, y por lo tanto, debilmente ligada a esta.
COMMON. Utilidades que no están en la SPL, tales como un autoloader de clases, un parser de anotaciones, estructuras avanzadas (p.ej: collections) y un sistema de caché.
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineInstalando Doctrine
Doctrine se puede instalar usando PEAR (para todo el sistema), o como dependencia del proyecto, mediante Composer.
El método recomendado es el de instalación mediante Composer, quedando PEAR para las versiones mas antiguas. Si no tenemos instalado Composer, podemos hacerlo tecleando en consola:
curl -sS http://getcomposer.org/installer | php
El archivo descargado (composer.phar) puede ser renombrado y movido a una carpeta que esté en el PATH de ejecución de usuario, para que pueda ser invocado desde cualquier punto. p.ej:
mv composer.phar /home/<user>/bin/composer
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineInstalando Doctrine
Una vez instalado Composer, para instalar Doctrine como dependencia de un proyecto basta con situarnos en su carpeta raíz, y creamos el fichero composer.json donde introducimos la dependencia:
{ "require": { "doctrine/orm": "*“ }}
La descarga e instalación de Doctrine se realiza tecleando en consola:
composer install
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineInstalando Doctrine
Al finalizar la descarga, la instalación habrá creado una nueva carpeta vendor que contendrá a Doctrine y sus dependencias, y un fichero composer.lock con el estado de las dependencias del proyecto.
Antes de poder usar Doctrine en nuestro proyecto debemos configurarlo de forma que pueda ser capaz de establecer conexión con el SGBD, y que el autoloader pueda cargar las clases de Doctrine, las entidades y el resto de clases de nuestro proyecto.
Veamos nuestro proyecto de ejemplo…
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
Creamos una carpeta test que será la raíz de nuestro proyecto (p.ej: /var/www/test ), y dentro de ella, creamos la siguiente estructura de carpetas:
bin.....Scripts de utilidades de nuestra aplicación.
config..Ficheros de configuración.
src.....Fuentes de Entidades, Clases, etc,etc.
web.....Carpeta pública de la aplicación.
Dentro de la carpeta raíz, creamos el fichero composer.json donde estableceremos las dependencias del proyecto y otros datos.
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
El fichero composer.json en la carpeta raíz:{ "name": "Test/Cine", "type": "project", "description": "Ejemplo para una introduccion a Doctrine", "require": { "doctrine/orm": "2.4.*", }, "autoload": { "psr-0": { "": "src/" } }}
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
Lo siguiente será configurar la aplicación para hacer uso de Doctrine y sus herramientas. En la carpeta config crearemos el fichero config.php:<?php// Configuracion de la aplicación// Acceso a la base de datos$dbParams = [ 'driver' =>'pdo_mysql', 'host' =>'127.0.0.1', 'dbname' =>'test', 'user' =>'test', 'password' =>'test‘];// Estamos en modo desarrollo?$dev = true;
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
En la carpeta src crearemos el bootstrap.php:<?php
use Doctrine\ORM\Tools\Setup;use Doctrine\ORM\EntityManager;
require_once __DIR__.'/../vendor/autoload.php';require_once __DIR__.'/../config/config.php';
$entitiesPath = array(__DIR__.'/Cine/Entity');
$config = Setup::createAnnotationMetadataConfiguration($entitiesPath, $dev);$entityManager = EntityManager::create($dbParams, $config);
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
En la carpeta config, creamos un fichero cli-config.php, para la configuración de las utilidades de línea de comandos (CLI) de Doctrine:<?php// Configuracion del CLI de Doctrine.// Dependencia del objeto ConsoleRunner use Doctrine\ORM\Tools\Console\ConsoleRunner;
// Incluimos el bootstrap para obtener el 'Entity Manager'require_once __DIR__.'/../src/bootstrap.php';
// Devolvemos el objeto HelperSet de consolareturn ConsoleRunner::createHelperSet($entityManager);
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
Si hemos relizado correctamente los pasos anteriores, deberíamos poder invocar Doctrine desde la consola, situándonos previamente en la carpeta raíz del proyecto y ejecutando:
php vendor/bin/doctrine.php
El proyecto ya tiene Doctrine instalado y configurado para usarse, tanto como capa de persistencia, como sus herramientas de consola.
Antes de empezar a crear entidades y manejarlas con Doctrine deberemos tener creada nuestra BD, con sus credenciales de acceso, tal y como se especificaron en el fichero config.php.
Introducción a Doctrine 2 ORM
Fundamentos de DoctrineUn proyecto de ejemplo
Nuestro proyecto de ejemplo contará con varias entidades relacionadas entre sí (Película, Comentario y Etiqueta), de forma que una película pueda tener comentarios y/o etiquetas, y cada una de estas últimas puede aparecer en una o varias películas. Inicialmente, tendremos:
Introducción a Doctrine 2 ORM
Película
• Id• Titulo• TituloOriginal• Director• Año
Comentario
• Id• Texto• Fecha
Etiqueta
• Nombre
Entidades y el mapeado
Introducción a Doctrine 2 ORM
Entidades y el mapeadoLas Entidades
Las entidades en Doctrine están definidas como clases PHP, que deben cumplir una serie de características:
No pueden ser final o contener métodos final. Las propiedades/atributos persistentes deben ser private o protected. No pueden implementar __clone() o __wakeup() , o si lo hacen, debe ser de
forma segura. No pueden usar func_get_args() para implementar un método con un número
variable de parámetros. Las propiedades/atributos persistentes solo deben accederse directamente
desde dentro de la entidad y por la instancia de la misma en si. El resto de accesos debe realizarse mediante los correspondientes getters y setters.
Introducción a Doctrine 2 ORM
Entidades y el mapeadoLas Entidades
Al crear una entidad, Doctrine no invoca al constructor de la clase, este solo es invocado si creamos una instancia con new. Esto permite el uso del constructor para la inicialización de estructuras, establecer valores por defecto o requerir parámetros.
En Doctrine, una entidad es un objeto con identidad. Se utiliza el patrón Identity Map para hacer un seguimiento de las entidades y sus ids, de tal forma que la instancia de la entidad con un determinado id sea única, sin importar las variables que apunten a ella, o el tipo de consulta usado.
Este patrón de seguimiento facilita el mantenimiento de los estados de la entidad a lo largo de su ciclo de vida.
Introducción a Doctrine 2 ORM
Entidades y el mapeadoEstados de una entidad
Introducción a Doctrine 2 ORM
•Estado de la instancia de la entidad, que no tiene una identidad persistente y no está asociada a un Entity Manager (p.ej: creada con el operador new).
NEW
MANAGED
DETACHED
REMOVED
•Estado de la instancia de la entidad, con identidad persitente, que está asociada a un Entity Manager, y por tanto, gestionada por el.
•Estado de la instancia de la entidad, con identidad persitente, pero que NO está asociada a un Entity Manager, y por tanto, NO gestionada por el.
•Estado de la instancia de la entidad, con identidad persitente, que está asociada a un Entity Manager, que será eliminada de la BD en la siguiente transacción.
Entidades y el mapeadoCiclo de vida de una entidad
Introducción a Doctrine 2 ORM
Entidades y el mapeadoTipos de datos y mapeado
El estado persistente de una entidad está representado por sus variables de instancia. Es decir, como objeto PHP, su estado persistente estará definido por el valor de sus campos/propiedades.
Estos campos o propiedades pueden ser de diferentes tipos. Aunque Doctrine soporta un amplio conjunto de tipos de datos, esto no significa que sea el mismo que el de los tipos nativos de PHP, o los del SGBD utilizado.
Mediante el mapeado de datos, informaremos a Doctrine de que campos definirán el estado de la entidad y el tipo de datos de cada uno.
El mapeado de datos, junto al de asociaciones, generan unos metadatos que sirven al ORM para gestionar las entidades y sus relaciones.
Introducción a Doctrine 2 ORM
Entidades y el mapeadoTipos de datos y mapeado
Doctrine provee de varias formas de generar el mapeado para metadatos:
Introducción a Doctrine 2 ORM
Anotaciones
•Los metadatos son incrustados dentro de bloques de comentarios, análogos a los utilizados por herramientas como PHPDocumentor.
•Es posible definir nuevos bloques de anotaciones.
XML
•Utiliza ficheros XML con un esquema propio para el mapeado de datos con Doctrine.
•Cada entidad debe tener su propio fichero descriptor, cuyo nombre será el Full Qualified Name de la entidad.
YML
•Utiliza el formato YML de documentos.
•Cada entidad debe tener su propio fichero descriptor, cuyo nombre será el Full Qualified Name de la entidad.
PHP
•Utiliza código nativo PHP mediante el API de la clase ClassMetadata.
•El código puede escribirse en ficheros o dentro de una función estática llamada loadMetadata($class) en la propia entidad.
Entidades y el mapeadoTipos de datos y mapeado
Introducción a Doctrine 2 ORM
Entidades y el mapeadoCreando las entidades de nuestro ejemplo
Podremos en práctica lo anterior, creando la clase inicial Pelicula, aunque de momento sin relacionar con las demás. Después iremos creando el resto de entidades.
Dentro de la carpeta src, crearemos la ruta Cine/Entity/ , que habíamos especificado previamente en nuestro bootstrap.php, y que servirá de almacen de nuestras clases para las entidades.
Usaremos el sistema de anotaciones para el mapeado de datos y asociaciones entre entidades. Nuestro fichero Pelicula.php contendrá lo siguiente:
Introducción a Doctrine 2 ORM
Entidades y el mapeadoCreando las entidades de nuestro ejemplo
src/Cine/Entity/Pelicula.php<?php
namespace Cine\Entity;
use Doctrine\ORM\Mapping\Entity;use Doctrine\ORM\Mapping\Table;use Doctrine\ORM\Mapping\Index;use Doctrine\ORM\Mapping\Id;use Doctrine\ORM\Mapping\GeneratedValue;use Doctrine\ORM\Mapping\Column;
/** * Pelicula * * @Entity * @Table( name="movie", indexes={ @Index(name="year_idx", columns="year") } ) * */class Pelicula {
Introducción a Doctrine 2 ORM
Entidades y el mapeadoCreando las entidades de nuestro ejemplo
/** * @var int * * @Id * @GeneratedValue * @Column(type="integer") */ private $id;
/** * @var string * * @Column(type="string", length=100, name="spanish_title") */ private $titulo;
/** * @var string * * @Column(type="string", length=100, name="original_title", nullable=false) */ private $tituloOriginal;
Introducción a Doctrine 2 ORM
Entidades y el mapeadoCreando las entidades de nuestro ejemplo
/** * @var string * * @Column(type="string", length=100) */ private $director; /** * @var int * * @Column(type="integer", name="year", nullable=false, unique=false, options={"unsigned":true, "default":0}) */ private $anyo;
}
Hacemos lo mismo con las otras entidades, creando los ficheros Comentario.php y Etiqueta.php en la misma carpeta.
Introducción a Doctrine 2 ORM
Entidades y el mapeadoCreando las entidades de nuestro ejemplo
Una vez creadas las clases de las entidades, Doctrine permite crear de forma automática los getters y los setters para cada una de las clases mediante línea de comandos. Situandonos en el directorio raíz de nuestro proyecto teclearemos:
php vendor/bin/doctrine.php orm:generate:entities src/
De la misma forma, Doctrine es capaz de crear de forma automática, el esquema de DB que corresponde a esas entidades:
php vendor/bin/doctrine.php orm:schema-tool:create
Esto debería haber creado la estructura de tablas en la BD, con sus campos definidos tal y como especificamos en la información definida en las anotaciones.
Introducción a Doctrine 2 ORM
Asociaciones entre entidades
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesTipos de asociaciones y cardinalidad
Introducción a Doctrine 2 ORM
UnidireccionalUnidireccional
• Las entidades relacionadas pueden ser obtenidas desde las entidades principales. Solo tienen lado propietario (owner).
BidireccionalBidireccional
• Las entidades relacionadas pueden ser obtenidas desde las principales, y a su vez, las principales pueden ser obtenidas desde las relacionadas. Tienen un lado propietario (owner) y un lado inverso (inverse).
TIP
OS
DE A
SO
CIA
CIO
NES
Asociaciones entre entidadesTipos de asociaciones y cardinalidad
Introducción a Doctrine 2 ORM
1 : 1 (Uno a Uno)1 : 1 (Uno a Uno)• Cada entidad principal solo puede tener una entidad asociada. • Se indica mediante la etiqueta @OneToOne
1 : N (Uno a muchos)1 : N (Uno a muchos)• Cada entidad principal puede tener varias entidades asociadas. • Se indica mediante la etiqueta @OneToMany
N : 1 (Muchos a Uno)N : 1 (Muchos a Uno)• Varias entidades tienen una misma entidad asociada. Solo disponible en
asociaciones bidireccionales, como parte inversa de una 1:N.• Se indica mediante la etiqueta @ManyToOne
N : N (Muchos a Muchos)N : N (Muchos a Muchos)• Varias entidades tienen asociadas otro conjunto de varias entidades.• Se indica mediante la etiqueta @ManyToMany
CA
RD
INA
LID
AD
Asociaciones entre entidadesHidratación de datos
En Doctrine, la hidratación (hydration) es el nombre que recibe el proceso de obtener un resultado final de una consulta a la BD y mapearlo a un ResultSet.
Los tipos de resultados que devuelve el proceso pueden ser:● Entidades● Arrays estrcuturados● Arrays escalares● Variables simples
Por lo general, la hidratación es un proceso que consume muchos recursos, por lo que recuperar solo los datos que vayamos a necesitar supondrá una mejora del rendimiento y/o consumo de dichos recursos.
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesHidratación de datos
Por defecto, Doctrine, en el resultado de una consulta, recupera la información de las entidades asociadas a la principal. Esto lo realiza empleando diferentes estrategias de recuperación, que pueden especificarse como valor del atributo fetch en las asociaciones:
Introducción a Doctrine 2 ORM
LAZY
• Es el valor por defecto (se puede obviar).
• Una vez cargados los datos de la entidad principal, los de las relacionadas se obtienen con una segunda consulta SQL.
EAGER
• Los datos de la entidad principal se recuperan junto a los de las entidades relacionadas haciendo uso de JOINs en una misma consulta.
EXTRA_LAZY
• Se cargan los datos de la entidad principal, pero los de en las entidades relacionadas solo tiene disponibles los métodos de la Collection que no impliquen la carga total.
Asociaciones entre entidadesParte propietaria y parte inversa
Doctrine solo gestiona la parte propietaria (owner) de una asociación. Esto significa que siempre deberemos identificar ese lado de la misma.
En una asociación bidireccional, la parte propietaria siempre tendrá un atributo inversedBy, y la parte inversa tendrá el atributo mappedBy.
Por defecto, las asociaciones @OneToOne y @ManyToOne son persistidas en SQL utilizando una columna con el id y una clave foránea. Las asociaciones @ManyToMany utilizan una tabla intermedia.
Los nombres de estas tablas y columnas son generados de forma automática por Doctrine, pero se pueden cambiar utilizando las anotaciones @JoinColumn y @JoinTable.
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Asociacion @ManyToOne entre las entidades Comentario y Peliculas (Lado propietario).
Editamos nuestra clase entidad Comentario.php y añadimos…
/** * @var Pelicula * * @ManyToOne( targetEntity="Pelicula", inversedBy="comentarios") */ private $pelicula;
Esto generará un nuevo campo en la tabla con el identificador de la pelicula a la que corresponde ese comentario, por defecto ‘pelicula_id’
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Asociación @OneToMany entre las entidades Comentario y Peliculas (Lado inverso).
Editamos nuestra clase entidad Pelicula.php y añadimos…use Doctrine\ORM\Mapping\OneToMany;use Doctrine\Common\Collections\ArrayCollection;…/** * @var Comentario[] * * @OneToMany( targetEntity="Comentario", mappedBy="pelicula") */private $comentarios;
La propiedad añadida almacena una colección de ids de entidades Comentario.
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Cambios en Peliculas.php… (cont)
Nuevo constructor:/** * Inicializamos colecciones */ public function __construct() { $this->comentarios = new ArrayCollection(); }
Abrimos una consola en la raíz de nuestro proyecto y (re)generamos los getters y setters de nuestras entidades modificadas:
php vendor/bin/doctrine.php orm:generate:entities src/
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Cambios en Peliculas.php… (cont)
El tipo de datos ArrayCollection de la propiedad, nos generará unos métodos AddComentario() y RemoveComentario(). Solo queda añadir una línea en el primero de ellos, que nos asegurará la persistencia de la parte propietaria de la asociación./** * Add comentarios * * @param \Cine\Entity\Comentario $comentarios * @return Pelicula */ public function addComentario(\Cine\Entity\Comentario $comentarios) { $this->comentarios[] = $comentarios; $comentarios->setPelicula($this); return $this; }
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Asociación @ManyToMany entre las entidades Etiquetas y Peliculas (Lado inverso).
Editamos el fichero Etiqueta.php y añadimos…use Doctrine\ORM\Mapping\ManyToMany;use Doctrine\Common\Collections\ArrayCollection;…/** * @var Pelicula[] * * @ManyToMany( targetEntity="Pelicula", mappedBy="etiquetas“ ) */ private $peliculas;
Creamos un constructor para inicializar la colección…
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Cambios en Etiqueta.php …(cont) // Inicializa coleccion public function __construct() { $this->peliculas = new ArrayCollection(); }
Implementamos el método __toString para poder hacer un ‘cast’ de la entidad a una cadena… // Cast del objeto como cadena public function __toString() { return $this->getNombre(); }
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Cambios en Etiqueta.php …(cont)
Generamos getters y setters de nuevo, lo que nos creará los métodos para la nueva propiedad: AddPelicula() y RemovePelicula(). Modificaremos el primero, añadiendo la línea que determina la persistencia del lado propietario: /** * Add películas * * @param \Cine\Entity\Pelicula $películas * @return Etiqueta */ public function addPelicula(\Cine\Entity\Pelicula $peliculas) { $this->peliculas[] = $peliculas; $peliculas->addEtiqueta($this); return $this; }
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Asociación @ManyToMany entre las entidades Etiquetas y Peliculas (Lado propietario).
Editamos el fichero Pelicula.php y añadimos…use Doctrine\ORM\Mapping\ManyToMany;use Doctrine\ORM\Mapping\JoinTable;use Doctrine\ORM\Mapping\JoinColumn;.../** * @var Etiqueta[] * * @ManyToMany( targetEntity="Etiqueta", inversedBy="peliculas", * fetch="EAGER", cascade={"persist"}, orphanRemoval=true * ) * @JoinTable( * name="movie_tag", * inverseJoinColumns={ @JoinColumn(name="tag_name", referencedColumnName="name") } * ) */ private $etiquetas;
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Cambios en el fichero Pelicula.php …(cont)
Finalmente, añadimos la inicialización de la nueva colección al cosntructor…// Inicializamos coleccionespublic function __construct(){ . . . $this->etiquetas = new ArrayCollection();}
Volvemos a generar los getters y setters, creándose los métodos AddEtiqueta() y RemoveEtiqueta(), aunque esta vez no hay que añadir la persistencia de la parte propietaria a AddEtiqueta(), ya que se hace de forma automática al haberlo establecido como un atributo de la asociación ( cascade={“Persist”} ).
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesParte propietaria y parte inversa
Tras los cambios realizados deberíamos poder obtener el esquema de la BD:
php vendor/bin/doctrine.php orm:schema-tool:update --force
Introducción a Doctrine 2 ORM
Una vez generadas nuestras entidades y el esquema de la base de datos correspondiente, es necesario introducir datos para poder empezar a crear el código de nuestra aplicación.
Doctrine posee una extensión (DataFixtures) destinada para este fin, que se instala a través de Composer, como una dependencia mas del proyecto.
Para ello, podemos editar el fichero composer.json de la carpeta ráiz del proyecto y añadir la dependencia y actualizar, o simplemente, decirle a composer que lo haga por nosotros. Desde la raíz del proyecto ejecutamos:
composer require doctrine/data-fixtures:1.*
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesUna extensión de Doctrine: DataFixtures
Una vez instalada extensión, iremos a la carpeta de nuestro proyecto y dentro de la ruta src/Cine, crearemos dentro una carpeta DataFixtures, al mismo nivel que Entity. Esta carpeta contendrá las clases (Fixtures) que harán la carga de datos para nuestras entidades.
Las clases de dicha carpeta han de implementar el FixtureInterface para poder ser utilizadas.
Su uso se realizará mediante la invocación de un script PHP (load-fixtures.php) que situaremos en la carpeta bin de nuestro proyecto y que invocaremos desde consola.
Ahora crearemos nuestras Fixtures y nuestro script de carga para el ejemplo…
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesUna extensión de Doctrine: DataFixtures
Contenido del fichero src/Cine/DataFixtures/LoadPeliculasData.php
<?php
namespace Cine\DataFixtures;
use Cine\Entity\Pelicula;use Doctrine\Common\DataFixtures\FixtureInterface;use Doctrine\Common\Persistence\ObjectManager;
class LoadPeliculasData implements FixtureInterface {
// Array de datos de ejemplo private $datos = [
[ ‘titulo’=>’’,’titulo_original’=>’’,’anyo’=>’’,’director’=>’’],. . .
[ ‘titulo’=>’’,’titulo_original’=>’’,’anyo’=>’’,’director’=>’’],] //array de arrays asociativos
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
// Metodo del interfaz 'FixtureInterface' a implementar public function load(ObjectManager $manager) {
foreach ($this->datos as $p) { // Creamos un nuevo objeto de la entidad (estado = NEW) $pelicula = new Pelicula(); // Informamos los atributos de nuestra entidad $película ->setTitulo( $p['titulo'] ) ->setTituloOriginal( $p['titulo_original'] ) ->setDirector( $p['director'] ) ->setAnyo( $p['anyo'] ); // Hacemos que la nueva entidad pase a ser gestionada (estado = MANAGED) $manager->persist($pelicula); } // Persistimos las entidades gestionadas $manager->flush(); }}
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
La entidad Comentarios es dependiente de la entidad Pelicula. Nuestra clase de Fixtures para la carga de comentarios será una clase que deberá implementar también el DependentFixtureInterface.
Fichero src/Cine/DataFixtures/LoadComentariosData.php:<?php
namespace Cine\DataFixtures;use Cine\Entity\Comentario;use Doctrine\Common\DataFixtures\DependentFixtureInterface;use Doctrine\Common\DataFixtures\FixtureInterface;use Doctrine\Common\Persistence\ObjectManager;
class LoadComentariosData implements FixtureInterface, DependentFixtureInterface {
// Array de datos de ejemplo private $datos = ['','',''...''];
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
// Metodo del interfaz 'FixtureInterface' a implementar public function load(ObjectManager $manager) { $numComentarios = 5; // Obtenemos la lista de películas $peliculas = $manager->getRepository('Cine\Entity\Pelicula')->findAll(); foreach ($peliculas as $p) { // # aleatorio de comentarios por pelicula (pero al menos uno). $total = mt_rand(1, $numComentarios); for ($i = 1; $i <= $total; $i++) { $comentario = new Comentario(); $comentario ->setTexto($this->datos[$i]) ->setFecha(new \DateTime(sprintf('-%d weeks', $total - $i))) ->setPelicula($p); // Gestionamos entidad $manager->persist($comentario); } } // Persistimos las entidades $manager->flush(); }
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
// Metodo del interfaz 'DependentFixtureInterface' a implementar public function getDependencies() { return ['Cine\DataFixtures\LoadPeliculasData']; }}
Y finalmente, src/Cine/DataFixtures/LoadEtiquetasData.php:<?php
namespace Cine\DataFixtures;use Cine\Entity\Etiqueta;use Doctrine\Common\DataFixtures\DependentFixtureInterface;use Doctrine\Common\DataFixtures\FixtureInterface;use Doctrine\Common\Persistence\ObjectManager;
class LoadEtiquetasData implements FixtureInterface, DependentFixtureInterface {
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
// Metodo del interfaz 'FixtureInterface' a implementar public function load(ObjectManager $manager) {
$num_etiquetas = 5;
// Preparamos un array de etiquetas $etiquetas = [];
for ($i = 1; $i <= $num_etiquetas; $i++) { $etiqueta = new Etiqueta(); $etiqueta->setNombre(sprintf("Etiqueta%d", $i)); $etiquetas[] = $etiqueta; }
// Obtenemos la lista de películas $peliculas = $manager->getRepository('Cine\Entity\Pelicula')->findAll();
// Agregamos un nuermo aleatorio de etiquetas a cada película $agregar = rand(1, $num_etiquetas);
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
foreach ($peliculas as $p) { for ($i = 0; $i < $agregar; $i++) { $p->addEtiqueta( $etiquetas[$i]); } $agregar = rand(1, $num_etiquetas); } // Persistimos entidades gestionadas $manager->flush(); }
// Metodo del interfaz 'DependentFixtureInterface' a implementar public function getDependencies() { return ['Cine\DataFixtures\LoadPeliculasData']; }}
Una vez implementadas nuestras Fixtures, debemos crear el script que las cargará y ejecutará. Este script (load-fixtures.php) lo situaremos en la carpeta bin de nuestro proyecto. Su contenido es el siguiente:
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
load-fixtures.php<?php// load-fixtures.php – Script de carga de datos para entidades// // Incluimos nuestro bootstrap para tener acceso al 'EntityManager‘require_once __DIR__.'/../src/bootstrap.php';
// Resolvemos dependencias de clasesuse Doctrine\Common\DataFixtures\Loader;use Doctrine\Common\DataFixtures\Purger\ORMPurger;use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
// Obtenemos un cargador y le indicamos la ruta de las Fixtures$loader = new Loader();$loader->loadFromDirectory(__DIR__.'/../src/Cine/DataFixtures');
// Objeto para purgado (vaciado) de entidades$purger = new ORMPurger();
// Objeto que ejecutara la carga de datos$executor = new ORMExecutor($entityManager, $purger);$executor->execute($loader->getFixtures());
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
Podemos comprobar el funcionamiento de la carag de datos, abriendo una consola enla carpeta raíz de nuestro proyecto y tecleando para:
…eliminar el esquema de la BD anterior:
php vendor/bin/doctrine.php orm:schema-tool:drop --force
…regenerar el esquema a partir de la ultimas definiciones de las entidades:
php vendor/bin/doctrine.php orm:schema-tool:create
…cargar nuestros datos a partir de las Fixtures:
php bin/load-fixtures.php
Introducción a Doctrine 2 ORM
Asociaciones entre entidadesInsertando datos en nuestro ejemplo
Consultas en Doctrine
Introducción a Doctrine 2 ORM
Introducción a Doctrine 2 ORM
Consultas en Doctrine
• DQL - Lenguaje de consulta específico del dominio Doctrine.
Doctrine Query
Language
• Helper Class para construcción de consultas mediante un API.QueryBuilder
• Uso de SQL propio del SGBD mediante NativeQuery o Query.SQL Nativo
Consultas en DoctrineDoctrine Query Language
Introducción a Doctrine 2 ORM
PROS Es similar a SQL, pero posee características propias (inspirado en
HQL). Gestiona objetos y propiedades en lugar de tablas y campos. Permite simplificar algunas construcciones de las consultas
gracias al uso de metadatos (p.ej: Claúsulas ON en los JOINS). Es Independiente del SGBD utilizado.
CONS Posee algunas diferencias de sintaxis con respecto al SQL
estándar. No posee toda la funcionalidad y optimizaciones del SQL
específico de cada SGBD. Tiene limitaciones a la hora de implementar ciertas consultas
(p.ej: subconsultas).
Consultas en DoctrineQueryBuilder
Introducción a Doctrine 2 ORM
Es una clase creada para ayudar a construir consultas DQL mediante el uso de un interfaz fluido, a través de un API.
El QueryBuilder se crea mediante el método createQueryBuilder() heredado del repositorio base de la entidad o desde el EntityManager.En la creación de una instancia de QueryBuilder desde el repositorio de la entidad debemos especificar un parámetro (string), que es el alias de la entidad principal.
$qb=$entityRepo->createQueryBuilder(‘u’);
Equivale a la creación desde el EntityManager:
$qb = $entityManager->createQueryBuilder();$qb->select(‘u’);
Consultas en DoctrineQueryBuilder
Introducción a Doctrine 2 ORM
Podemos obtener la consulta DQL generada por el QueryBuilder mediante el uso de su método getDQL().
$qb = $entityManager->createQueryBuilder();$qb->select('p')->from('Cine\Entity\Pelicula','p');$queryDQL = $qb->getDQL();
De forma similar, previa obtención del objeto Query asociado al QueryBuilder, podemos acceder al SQL resultante mediante el método getSQL().
$query = $qb->getQuery();$querySQL = $query->getSQL();
Consultas en DoctrineQueryBuilder
Introducción a Doctrine 2 ORM
Las consultas DQL hacen uso interno de los Prepared Statements de SQL, por motivos de seguridad (p.ej: SQL injections) y rendimiento (unidad transaccional).
Por defecto, y a menos que se especifique otra cosa, una consulta DQL obtiene todas las entidades relacionadas con la principal. Esto puede provocar problemas de recursos o de rendimiento si no se gestiona bien.
La naturaleza de las relaciones entre entidades es conocida por Doctrine gracias a los metadatos de sus asociaciones, por ello no es necesario especificar cláusulas ON o USING en los JOIN.
Las clases QueryBuilder y Query gestionan un caché de las consultas. El comportamiento y naturaleza de este caché es diferente según estemos en modo desarrollo o producción.
Consultas en DoctrineSQL Nativo
Introducción a Doctrine 2 ORM
• Los resultados se mapean a entidades Doctrine mediante ResultSetMapBuilder.
• Se utiliza Doctrine ORM.• Solo se soportan consultas SELECT.
NativeQuery
• Los resultados no se mapean a entidades Doctrine.• Se utiliza Doctrine DBAL.Query
Consultas en DoctrineSQL Nativo
Introducción a Doctrine 2 ORM
Usando NativeQuery (mapeando a entidades)
$rsmb = new ResultSetMappingBuilder($entityManager);$rsmb->addRootEntityFromClassMetadata('Cine\Entity\Pelicula', 'p');$rsmb->addJoinedEntityFromClassMetadata( 'Cine\Entity\Comentario', 'c', 'p', 'comentarios', [ 'id' => 'comment_id' ]); $sql = <<<SQLSELECT movie.id, movie.original_title, comment.id as comment_id, comment.texto, comment.fechaFROM movie INNER JOIN comment ON movie.id = comment.pelicula_idWHERE movie.year >= 1988ORDER BY movie.id, comment.fechaSQL;$query = $entityManager->createNativeQuery($sql, $rsmb);$result = $query->getResult();
Consultas en DoctrineSQL Nativo
Introducción a Doctrine 2 ORM
Usando Query (sin mapeado a entidades)
$sql = <<<SQLSELECT spanish_title AS titulo, COUNT(comment.id) AS comentarios FROM movie JOIN comment ON comment.pelicula_id = movie.idGROUP BY movie.idORDER BY comentarios DESC, spanish_title ASCLIMIT 5;SQL;
$query = $entityManager->getConnection()->query($sql);$result = $query->fetchAll();
Consultas en DoctrineRepositorios de Entidades Personalizados
Introducción a Doctrine 2 ORM
Cuando generamos una entidad, Doctrine nos proporciona un repositorio base por defecto para dicha entidad.El repositorio base consta de una serie de métodos comunes para operar con la entidad:
find(id)Retorna la entidad con el identificador id, o null si no existe.
findAll()Retorna un array con todas las entidades del repositorio.
findBy( array(criterios) [, array(ordenacion)] )Retorna un array con las entidades que cumplan los criterios especificados en el primer parámetro, y ordenados por los del segundo parámetro (opcional).
findOneBy( array(criterios) ) Análogo al findBy() pero devolviendo solo un elemento, o nulo si no existe.
Consultas en DoctrineRepositorios de Entidades Personalizados
Introducción a Doctrine 2 ORM
Algunos de los métodos del repositorio base pueden utilizarse de forma abreviada, permitiendo no especificar como parámetro el nombre de la propiedad. P.ej:
findByTitulo(‘valor’), findOneByTitulo(‘valor’)
Equivalen a:
findBy(‘Titulo’=>’valor’), findOneBy(‘Titulo’=>’valor’)
Esto se debe a la utilización del método ‘mágico’ __call() de PHP que hace Doctrine a la hora de localizar el nombre del método de la clase.
Consultas en DoctrineRepositorios de Entidades Personalizados
Introducción a Doctrine 2 ORM
El repositorio base de una entidad puede ser extendido a fin de proporcionar métodos específicos para consultas de usuario.
Este repositorio personalizado permite optimizar la obtención de resultados en las consultas, obteniendo mas control en la hidratación de los datos (p.ej: seleccionar parcialmente entidades y/o especificar operaciones en la consulta que no se harían de forma automática).
Doctrine permite especificar la clase que utilizaremos como repositorio de la entidad en la etiqueta @Entity, dentro de las anotaciones de dicha entidad.
@Entity(repositoryClass=“PeliculaRepository”)
La clase (vacía) será generada mediante las herramientas de consola (CLI) de Doctrine, ejecutando:
php vendor/bin/doctrine.php orm:generate-repositories /src
Consultas en DoctrineOptimizando nuestro ejemplo
Una optimización básica de nuestro ejemplo sería la construcción de un repertorio personalizado para la entidad Pelicula.
Este constará de métodos que nos permitan recuperar solamente los datos que necesitamos mostrar en cada momento. Para ello crearemos consultas DQL de forma que…
Se seleccionen solo los campos específicos de las entidades asociadas en lugar de recuperar todos ellos (comportamiento por defecto).
Se haga en una sola operación lo que de otra forma requeriría mas de una (p.ej: Recuperar datos de una entidad asociada con fetch=‘lazy’).
Introducción a Doctrine 2 ORM
public function findConNumComentarios(){
return $this
->createQueryBuilder('p')
->leftJoin('p.comentarios', 'c')
->addSelect('COUNT(c.id)')
->groupBy('p.id')
->getQuery()
->getResult();
}
Introducción a Doctrine 2 ORM
Consultas en DoctrineOptimizando nuestro ejemplo
public function findTeniendoEtiquetas(array $etiquetas) {
return $queryBuilder = $this
->createQueryBuilder('p')
->addSelect('e.nombre
->addSelect('COUNT(c.id)')
->join('p.etiquetas', 'e')
->leftJoin('p.comentarios', 'c')
->where('e.nombre IN (:etiquetas)')
->groupBy('p.id')
->having('COUNT(e.nombre) >= :numEtiquetas')
->setParameter('etiquetas', $etiquetas)
->setParameter('numEtiquetas', count($etiquetas))
->getQuery()
->getResult();
}
Introducción a Doctrine 2 ORM
Consultas en DoctrineOptimizando nuestro ejemplo
public function findConComentarios($id) {
return $this
->createQueryBuilder('p')
->addSelect('c')
->leftJoin('p.comentarios', 'c')
->where('p.id = :id')
->orderBy('c.fecha', 'ASC')
->setParameter('id', $id)
->getQuery()
->getOneOrNullResult();
}
Introducción a Doctrine 2 ORM
Consultas en DoctrineOptimizando nuestro ejemplo
Gracias por su atención
[email protected] de ejemplo
https://github.com/gonfert/cine
RecursosDoctrine Project Site
http://www.doctrine-project.org
Notes on Doctrine 2http://www.krueckeberg.org/notes/d2.html
Mastering Symfony2 performancehttp://labs.octivi.com/mastering-symfony2-
performance-doctrine/
BibliografíaPersistence in PHP with Doctrine ORM
(Packt Publishing)
Proyectos similaresPropelhttp://propelorm.org
Red Bean PHP4http://redbeanphp.com
Spot ORMhttp://phpdatamapper.com
Introducción a Doctrine 2 ORM