O slideshow foi denunciado.
Utilizamos seu perfil e dados de atividades no LinkedIn para personalizar e exibir anúncios mais relevantes. Altere suas preferências de anúncios quando desejar.

Introduccion a Doctrine 2 ORM

3.138 visualizações

Publicada em

Introducción a Doctrine 2 ORM.

Una introducción y uso básico de Doctrine 2 ORM en PHP sin utilizar frameworks, a través de un proyecto sencillo usado como ejemplo.

El codigo PHP del proyecto se puede descargar de su repositorio de Github: (https://github.com/gonfert/cine.git)

Presentación realizada para la X Symfony Zaragoza

Publicada em: Software

Introduccion a Doctrine 2 ORM

  1. 1. Introducción a Doctrine 2 ORM Por J.R.Laguardia
  2. 2. 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
  3. 3. Fundamentos de Doctrine Introducción a Doctrine 2 ORM
  4. 4. Fundamentos de Doctrine Que 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
  5. 5. Fundamentos de Doctrine Que 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
  6. 6. Fundamentos de Doctrine Que 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.
  7. 7. Fundamentos de Doctrine Componentes 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
  8. 8. Fundamentos de Doctrine Instalando 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
  9. 9. Fundamentos de Doctrine Instalando 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
  10. 10. Fundamentos de Doctrine Instalando 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
  11. 11. Fundamentos de Doctrine Un 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
  12. 12. Fundamentos de Doctrine Un 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
  13. 13. Fundamentos de Doctrine Un 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
  14. 14. Fundamentos de Doctrine Un proyecto de ejemplo En la carpeta src crearemos el bootstrap.php: <?php use DoctrineORMToolsSetup; use DoctrineORMEntityManager; 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
  15. 15. Fundamentos de Doctrine Un 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 DoctrineORMToolsConsoleConsoleRunner; // Incluimos el bootstrap para obtener el 'Entity Manager' require_once __DIR__.'/../src/bootstrap.php'; // Devolvemos el objeto HelperSet de consola return ConsoleRunner::createHelperSet($entityManager); Introducción a Doctrine 2 ORM
  16. 16. Fundamentos de Doctrine Un 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
  17. 17. Fundamentos de Doctrine Un 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
  18. 18. Entidades y el mapeado Introducción a Doctrine 2 ORM
  19. 19. Entidades y el mapeado Las 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
  20. 20. Entidades y el mapeado Las 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
  21. 21. Entidades y el mapeado Estados 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.
  22. 22. Entidades y el mapeado Ciclo de vida de una entidad Introducción a Doctrine 2 ORM
  23. 23. Entidades y el mapeado Tipos 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
  24. 24. Entidades y el mapeado Tipos 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($cla ss) en la propia entidad.
  25. 25. Entidades y el mapeado Tipos de datos y mapeado Introducción a Doctrine 2 ORM
  26. 26. Entidades y el mapeado Creando 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
  27. 27. Entidades y el mapeado Creando las entidades de nuestro ejemplo src/Cine/Entity/Pelicula.php <?php namespace CineEntity; use DoctrineORMMappingEntity; use DoctrineORMMappingTable; use DoctrineORMMappingIndex; use DoctrineORMMappingId; use DoctrineORMMappingGeneratedValue; use DoctrineORMMappingColumn; /** * Pelicula * * @Entity * @Table( name="movie", indexes={ @Index(name="year_idx", columns="year") } ) * */ class Pelicula { Introducción a Doctrine 2 ORM
  28. 28. Entidades y el mapeado Creando 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
  29. 29. Entidades y el mapeado Creando 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
  30. 30. Entidades y el mapeado Creando 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
  31. 31. Asociaciones entre entidades Introducción a Doctrine 2 ORM
  32. 32. Asociaciones entre entidades Tipos 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). TIPOSDEASOCIACIONES
  33. 33. Asociaciones entre entidades Tipos 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 CARDINALIDAD
  34. 34. Asociaciones entre entidades Hidratació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
  35. 35. Asociaciones entre entidades Hidratació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.
  36. 36. Asociaciones entre entidades Parte 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
  37. 37. Asociaciones entre entidades Parte 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
  38. 38. Asociaciones entre entidades Parte 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 DoctrineORMMappingOneToMany; use DoctrineCommonCollectionsArrayCollection; … /** * @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
  39. 39. Asociaciones entre entidades Parte 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
  40. 40. Asociaciones entre entidades Parte 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 CineEntityComentario $comentarios * @return Pelicula */ public function addComentario(CineEntityComentario $comentarios) { $this->comentarios[] = $comentarios; $comentarios->setPelicula($this); return $this; } Introducción a Doctrine 2 ORM
  41. 41. Asociaciones entre entidades Parte propietaria y parte inversa Asociación @ManyToMany entre las entidades Etiquetas y Peliculas (Lado inverso). Editamos el fichero Etiqueta.php y añadimos… use DoctrineORMMappingManyToMany; use DoctrineCommonCollectionsArrayCollection; … /** * @var Pelicula[] * * @ManyToMany( targetEntity="Pelicula", mappedBy="etiquetas“ ) */ private $peliculas; Creamos un constructor para inicializar la colección… Introducción a Doctrine 2 ORM
  42. 42. Asociaciones entre entidades Parte 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
  43. 43. Asociaciones entre entidades Parte 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 CineEntityPelicula $películas * @return Etiqueta */ public function addPelicula(CineEntityPelicula $peliculas) { $this->peliculas[] = $peliculas; $peliculas->addEtiqueta($this); return $this; } Introducción a Doctrine 2 ORM
  44. 44. Asociaciones entre entidades Parte propietaria y parte inversa Asociación @ManyToMany entre las entidades Etiquetas y Peliculas (Lado propietario). Editamos el fichero Pelicula.php y añadimos… use DoctrineORMMappingManyToMany; use DoctrineORMMappingJoinTable; use DoctrineORMMappingJoinColumn; ... /** * @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
  45. 45. Asociaciones entre entidades Parte 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 colecciones public 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
  46. 46. Asociaciones entre entidades Parte 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
  47. 47. 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 entidades Una extensión de Doctrine: DataFixtures
  48. 48.  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 entidades Una extensión de Doctrine: DataFixtures
  49. 49. Contenido del fichero src/Cine/DataFixtures/LoadPeliculasData.php <?php namespace CineDataFixtures; use CineEntityPelicula; use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonPersistenceObjectManager; 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 entidades Insertando datos en nuestro ejemplo
  50. 50. // 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 entidades Insertando datos en nuestro ejemplo
  51. 51. 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 CineDataFixtures; use CineEntityComentario; use DoctrineCommonDataFixturesDependentFixtureInterface; use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonPersistenceObjectManager; class LoadComentariosData implements FixtureInterface, DependentFixtureInterface { // Array de datos de ejemplo private $datos = ['','',''...'']; Introducción a Doctrine 2 ORM Asociaciones entre entidades Insertando datos en nuestro ejemplo
  52. 52. // Metodo del interfaz 'FixtureInterface' a implementar public function load(ObjectManager $manager) { $numComentarios = 5; // Obtenemos la lista de películas $peliculas = $manager->getRepository('CineEntityPelicula')->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 entidades Insertando datos en nuestro ejemplo
  53. 53. // Metodo del interfaz 'DependentFixtureInterface' a implementar public function getDependencies() { return ['CineDataFixturesLoadPeliculasData']; } } Y finalmente, src/Cine/DataFixtures/LoadEtiquetasData.php: <?php namespace CineDataFixtures; use CineEntityEtiqueta; use DoctrineCommonDataFixturesDependentFixtureInterface; use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonPersistenceObjectManager; class LoadEtiquetasData implements FixtureInterface, DependentFixtureInterface { Introducción a Doctrine 2 ORM Asociaciones entre entidades Insertando datos en nuestro ejemplo
  54. 54. // 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('CineEntityPelicula')->findAll(); // Agregamos un nuermo aleatorio de etiquetas a cada película $agregar = rand(1, $num_etiquetas); Introducción a Doctrine 2 ORM Asociaciones entre entidades Insertando datos en nuestro ejemplo
  55. 55. 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 ['CineDataFixturesLoadPeliculasData']; } } 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 entidades Insertando datos en nuestro ejemplo
  56. 56. 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 clases use DoctrineCommonDataFixturesLoader; use DoctrineCommonDataFixturesPurgerORMPurger; use DoctrineCommonDataFixturesExecutorORMExecutor; // 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 entidades Insertando datos en nuestro ejemplo
  57. 57. 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 entidades Insertando datos en nuestro ejemplo
  58. 58. Consultas en Doctrine Introducción a Doctrine 2 ORM
  59. 59. 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
  60. 60. Consultas en Doctrine Doctrine 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).
  61. 61. Consultas en Doctrine QueryBuilder 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’);
  62. 62. Consultas en Doctrine QueryBuilder 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('CineEntityPelicula','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();
  63. 63. Consultas en Doctrine QueryBuilder 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.
  64. 64. Consultas en Doctrine SQL 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
  65. 65. Consultas en Doctrine SQL Nativo Introducción a Doctrine 2 ORM Usando NativeQuery (mapeando a entidades) $rsmb = new ResultSetMappingBuilder($entityManager); $rsmb->addRootEntityFromClassMetadata('CineEntityPelicula', 'p'); $rsmb->addJoinedEntityFromClassMetadata( 'CineEntityComentario', 'c', 'p', 'comentarios', [ 'id' => 'comment_id' ] ); $sql = <<<SQL SELECT movie.id, movie.original_title, comment.id as comment_id, comment.texto, comment.fecha FROM movie INNER JOIN comment ON movie.id = comment.pelicula_id WHERE movie.year >= 1988 ORDER BY movie.id, comment.fecha SQL; $query = $entityManager->createNativeQuery($sql, $rsmb); $result = $query->getResult();
  66. 66. Consultas en Doctrine SQL Nativo Introducción a Doctrine 2 ORM Usando Query (sin mapeado a entidades) $sql = <<<SQL SELECT spanish_title AS titulo, COUNT(comment.id) AS comentarios FROM movie JOIN comment ON comment.pelicula_id = movie.id GROUP BY movie.id ORDER BY comentarios DESC, spanish_title ASC LIMIT 5; SQL; $query = $entityManager->getConnection()->query($sql); $result = $query->fetchAll();
  67. 67. Consultas en Doctrine Repositorios 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.
  68. 68. Consultas en Doctrine Repositorios 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.
  69. 69. Consultas en Doctrine Repositorios 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
  70. 70. Consultas en Doctrine Optimizando 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
  71. 71. 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 Doctrine Optimizando nuestro ejemplo
  72. 72. 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 Doctrine Optimizando nuestro ejemplo
  73. 73. 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 Doctrine Optimizando nuestro ejemplo
  74. 74. Gracias por su atención jrlaguardia@gmail.com Proyecto de ejemplo https://github.com/gonfert/cine Recursos Doctrine Project Site http://www.doctrine-project.org Notes on Doctrine 2 http://www.krueckeberg.org/notes/d2.html Mastering Symfony2 performance http://labs.octivi.com/mastering-symfony2- performance-doctrine/ Bibliografía Persistence in PHP with Doctrine ORM (Packt Publishing) Proyectos similares Propel http://propelorm.org Red Bean PHP4 http://redbeanphp.com Spot ORM http://phpdatamapper.com Introducción a Doctrine 2 ORM

×