Introduccion a Doctrine 2 ORM

74
Introducción a Doctrine 2 ORM Por J.R.Laguardia

Transcript of Introduccion a Doctrine 2 ORM

Page 1: Introduccion a Doctrine 2 ORM

Introducción a Doctrine 2 ORM

Por J.R.Laguardia

Page 2: Introduccion a Doctrine 2 ORM

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

Page 3: Introduccion a Doctrine 2 ORM

Fundamentos de Doctrine

Introducción a Doctrine 2 ORM

Page 4: Introduccion 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

Page 5: Introduccion 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

Page 6: Introduccion 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.

Page 7: Introduccion a Doctrine 2 ORM

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

Page 8: Introduccion 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

Page 9: Introduccion 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

Page 10: Introduccion 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

Page 11: Introduccion 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

Page 12: Introduccion 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

Page 13: Introduccion 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

Page 14: Introduccion 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

Page 15: Introduccion 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

Page 16: Introduccion 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

Page 17: Introduccion 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

Page 18: Introduccion a Doctrine 2 ORM

Entidades y el mapeado

Introducción a Doctrine 2 ORM

Page 19: Introduccion 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

Page 20: Introduccion 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

Page 21: Introduccion 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.

Page 22: Introduccion a Doctrine 2 ORM

Entidades y el mapeadoCiclo de vida de una entidad

Introducción a Doctrine 2 ORM

Page 23: Introduccion 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

Page 24: Introduccion 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.

Page 25: Introduccion a Doctrine 2 ORM

Entidades y el mapeadoTipos de datos y mapeado

Introducción a Doctrine 2 ORM

Page 26: Introduccion 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

Page 27: Introduccion 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

Page 28: Introduccion 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

Page 29: Introduccion 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

Page 30: Introduccion 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

Page 31: Introduccion a Doctrine 2 ORM

Asociaciones entre entidades

Introducción a Doctrine 2 ORM

Page 32: Introduccion 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

Page 33: Introduccion a Doctrine 2 ORM

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

Page 34: Introduccion a Doctrine 2 ORM

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

Page 35: Introduccion 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.

Page 36: Introduccion a Doctrine 2 ORM

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

Page 37: Introduccion 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

Page 38: Introduccion 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

Page 39: Introduccion 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

Page 40: Introduccion 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

Page 41: Introduccion 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

Page 42: Introduccion 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

Page 43: Introduccion 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

Page 44: Introduccion 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

Page 45: Introduccion 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

Page 46: Introduccion 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

Page 47: Introduccion 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

Page 48: Introduccion a Doctrine 2 ORM

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

Page 49: Introduccion a Doctrine 2 ORM

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

Page 50: Introduccion a Doctrine 2 ORM

// 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

Page 51: Introduccion a Doctrine 2 ORM

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

Page 52: Introduccion a Doctrine 2 ORM

// 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

Page 53: Introduccion a Doctrine 2 ORM

// 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

Page 54: Introduccion a Doctrine 2 ORM

// 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

Page 55: Introduccion a Doctrine 2 ORM

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

Page 56: Introduccion a Doctrine 2 ORM

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

Page 57: Introduccion a Doctrine 2 ORM

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

Page 58: Introduccion a Doctrine 2 ORM

Consultas en Doctrine

Introducción a Doctrine 2 ORM

Page 59: Introduccion 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

Page 60: Introduccion a Doctrine 2 ORM

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).

Page 61: Introduccion a Doctrine 2 ORM

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’);

Page 62: Introduccion a Doctrine 2 ORM

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();

Page 63: Introduccion a Doctrine 2 ORM

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.

Page 64: Introduccion a Doctrine 2 ORM

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

Page 65: Introduccion a Doctrine 2 ORM

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();

Page 66: Introduccion a Doctrine 2 ORM

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();

Page 67: Introduccion a Doctrine 2 ORM

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.

Page 68: Introduccion a Doctrine 2 ORM

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.

Page 69: Introduccion a Doctrine 2 ORM

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

Page 70: Introduccion a Doctrine 2 ORM

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

Page 71: Introduccion 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

Page 72: Introduccion a Doctrine 2 ORM

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

Page 73: Introduccion a Doctrine 2 ORM

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

Page 74: Introduccion a Doctrine 2 ORM

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