Sie sind auf Seite 1von 15

Doctrine ORM

Qu es Doctrine?

Doctrine es un ORM que implementa el patron Data Mapper y permite crear una separacin clara
entre las reglas de negocio de la aplicacin y la capa persistente de la base de datos.
Algunas de las ventajas de ORM son:
Ms rpido y fcil de usar.
Entidades son solo objetos planos.
Doctrine usa el enfoque code first, as que puedes primero crear entidades, y luego
generar una base de datos para ellos automticamente. El caso contrario es posible, pero
no recomendado.
Soporta anotaciones, XML y YAML para el esquema de base de datos.
DQL(un reemplazo de SQL) realiza la abstraccin de tus tablas.
Los eventos de Doctrine permiten fcilmente detectar eventos especficos de base de
datos y realizar ciertas acciones.
Los repositorios son ms fieles al patrn Repository.
La metodologa transaccional write-behind permite a Doctrine tener menor interaccin con
la base de datos hasta que realice una llamada explcita al mtodo flush().

Mapeo de tablas

Doctrine tiene varios mtodos para el mapeo de clases a tablas en la base de datos. Estos mtodos
incluyen, definicin en PHP, XML, YML y anotaciones, siendo este ultimo el ms comn y usado.

El siguiente codigo correspondoe a todas las opciones del mapeo de un entity


/**
*
* @ORM\Table(
* schema="item_record",
* name="item_record",
* options={
* "charset":"utf8",
* "collate":"utf8_unicode_ci",
* "comment":"library record of a work",
* "temporary":false,
* "engine":"InnoDB"
* },
* indexes={
* @ORM\Index(name="ix_name", columns={"itemRecord_name"}),
* @ORM\Index(name="ix_name_publisher", columns={"itemRecord_publisherId","itemRecord_name"})
* },
* uniqueConstraints={
* @ORM\UniqueConstraint(name="ix_name_ean", columns={"itemRecord_name","eanId"}),

1
* @ORM\UniqueConstraint(name="ix_ean_publisher", columns= {"itemRecord_publisherId","eanId"})
* }
*)
* @ORM\DiscriminatorMap(
* {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
*)
* @ORM\DiscriminatorColumn(name="item", type="string")
* @ORM\InheritanceType("JOINED")
*
*
*
* @ORM\HasLifecycleCallbacks
* @ORM\ChangeTrackingPolicy("DEFERRED_IMPLICIT")
* @ORM\Entity(repositoryClass="Doctrine\ORM\EntityRepository")
*/

A continuacion vamos a enumerar las opciones de mapeo con todas sus opciones:

Id
/**
* @ORM\Id
* @ORM\Column(
* type="integer",
* name="itemRecord_id",
* length=255
* columnDefinition="itemRecord_id",
* precision=3,
* scale=3,
* options={"unsigned":true,"comment":"this is primary key","version":2}
*)
* @ORM\Version
* @ORM\GeneratedValue(strategy="SEQUENCE")
*/
private $id;

Field
/**
* @ORM\Column(
* type="string",
* length=255,
* nullable=false,
* name="itemRecord_name",
* columnDefinition="itemRecord_name",
* precision=1,
* scale=1,
* options={"comment":"this is field","unsigned":true,"version":3}
*)

2
*/
private $item;

Index
/**
* @ORM\Entity
* @ORM\Table(
* uniqueConstraints={@ORM\UniqueConstraint(name="ix_first_name_last_name_date",
columns={"firstName","lastName","birthDate"})}
*)
*/
class author

Asociaciones
Uno a Uno:
/**
* @ORM\Entity
*/
class itemRecord
{
/**
* @ORM\OneToOne(
* targetEntity="ean",
* inversedBy="itemRecord"
*)
* @ORM\JoinColumn(name="eanId", referencedColumnName="id", unique=true)
*/
private $ean;
}

Uno a uno, lado inverso.


/**
* @ORM\Entity
*/
class ean
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @ORM\OneToOne(
* targetEntity="itemRecord",
* mappedBy="ean"

3
*)
*/
private $itemRecord;
}

Muchos a Uno:
/**
* @ORM\Entity
*/
class itemRecord
{
/**
* @ORM\ManyToOne(
* targetEntity="publisher",
* inversedBy="itemRecord"
*)
* @ORM\JoinColumn(
* name="publisherId",
* referencedColumnName="publisher_id",
* nullable=false
*)
*/
private $publisher;
}
Muchos a Uno, lado inverso:

/**
* @ORM\Entity
*/
class publisher
{
/**
* @ORM\Id
*/
private $id;

/**
* @ORM\OneToMany(
* targetEntity="itemRecord",
* mappedBy="publisher"
*)
*/
private $itemRecord;
}
Todas las opciones de una asociacion:
/**
* @ORM\Entity
*/
class itemRecord
{
/**
* @ORM\ManyToOne(
* targetEntity="publisher",
* inversedBy="itemRecord",
* fetch="EXTRA_LAZY",
* orphanRemoval=true,
* cascade={"all","merge","persist","refresh","remove"}
*)

4
* @ORM\JoinColumn(
* name="publisherId",
* referencedColumnName="publisher_id",
* nullable=false,
* columnDefinition="itemRecord_publisherId",
* onDelete="CASCADE",
* onUpdate="RESTRICT"
*)
*/
private $publisher;
}

Lado invero con todas las opciones:


/**
* @ORM\Entity
*/
class publisher
{
/**
* @ORM\Id
* @ORM\Column(
* type="integer"
*)
* @ORM\GeneratedValue(strategy="SEQUENCE")
*/
private $id;

/**
* @ORM\OneToMany(
* targetEntity="itemRecord",
* mappedBy="publisher",
* fetch="EAGER",
* indexBy="id",
* cascade={"all","merge","persist","refresh","remove"}
*)
* @ORM\OrderBy({"id"="ASC"})
*/
private $itemRecord;
}

Muchos a Muchos:
/**
* @ORM\Entity
*/
class author
{
/**
* @ORM\Id
*/
private $id;

/**
* @ORM\ManyToMany(targetEntity="itemRecord", inversedBy="author")
* @ORM\JoinTable(
* name="itemRecordHasAuthor",
* joinColumns={
* @ORM\JoinColumn(
* name="authorId",
* referencedColumnName="id",
* nullable=false
* )

5
* },
* inverseJoinColumns={@ORM\JoinColumn(name="bookId", referencedColumnName="itemRecord_id", nullable=false)}
*)
*/
private $itemRecord;
}

Muchos a Muchos, lado inverso:


/**
* @ORM\Entity
*/
class itemRecord
{
/**
* @ORM\ManyToMany(targetEntity="author", mappedBy="itemRecord")
*/
private $author;
}

Todas las opciones de Muchos a Muchos:


/**
* @ORM\Entity
*/
class author
{
/**
* @ORM\Id
*/
private $id;

/**
* @ORM\ManyToMany(targetEntity="itemRecord", inversedBy="author", cascade={"all","refresh"})
* @ORM\JoinTable(
* name="itemRecordHasAuthor",
* joinColumns={
* @ORM\JoinColumn(
* name="authorId",
* referencedColumnName="id",
* nullable=false,
* fetch="EAGER",
* onDelete="CASCADE",
* onUpdate="RESTRICT"
* )
* },
* inverseJoinColumns={@ORM\JoinColumn(name="bookId", referencedColumnName="itemRecord_id", nullable=false)}
*)
* @ORM\OrderBy({"id"="ASC"})
*/
private $itemRecord;
}

6
Herencia
Doctrine, maneja tres tipos de herencia.
Herencia con una tabla:

/**
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="item", type="string")
* @ORM\DiscriminatorMap(
* {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
*)
*/
class itemRecord

Clase Hija:
/**
* @ORM\Entity
*/
class book extends itemRecord

Herencia de tablas:
/**
* @ORM\Entity
* @ORM\InheritanceType("JOINED")
* @ORM\DiscriminatorColumn(name="item", type="string")
* @ORM\DiscriminatorMap(
* {"itemRecord"="itemRecord","book"="book","magazine"="magazine","audioRecord"="audioRecord"}
*)
*/
class itemRecord

Clase hija:
/**
* @ORM\Entity
*/
class book extends itemRecord

Superclase mapeada:
/**
*
* @ORM\MappedSuperclass(repositoryClass="Doctrine\ORM\EntityRepository")
*/
class itemRecord

Clase hija:
/**
* @ORM\Entity
*/
class book extends itemRecord

7
Consultas en Doctrine

Existen varias maneras de realizar consultas a la base de datos con doctrine, si bien se pueden
realizar consultas de forma directa con SQL, muchas veces lo recomendable es hacerlas de forma
estndar para no sufrir durante cambios de motor de base de datos.
Para este tipo de tareas Doctrine cuenta con DQL, que significa Doctrine Query Language(Lenguaje
de consultas de Doctrine). DQL incluye el lenguaje de creacin de query a travs de objetos, lo que
significa que en lugar de las consultas tradicionales, podrs realizar consultas usando objetos.

Ejemplos de consultas con DQL:


$query = $em->createQuery('SELECT u FROM MyProject\Model\User u');

$query = $em->createQuery('SELECT u.id FROM CmsUser u');

$query = $em->createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u');

$query = $em->createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC');

$query = $em->createQuery('SELECT u.username, u.name FROM CmsUser u');

$query = $em->createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a');

Consultas con Query Builder

Doctrine cuenta con una forma programtica de realizar las consultas, esto permite mayor control
de la consulta, ya que se puede realizar muchos cambios de forma dinmica.

$qb = $em->createQueryBuilder();
$qb->select(array('u'))
->from('User', 'u')
->where($qb->expr()->orX(
$qb->expr()->eq('u.id', '?1'),
$qb->expr()->like('u.nickname', '?2')
))
->orderBy('u.surname', 'ASC');

Consulta Nativas

8
La otra forma de realizar consultas, es hacerlas con SQL directo:

use Doctrine\ORM\Query\ResultSetMappingBuilder;

$sql = "SELECT u.id, u.name, a.id AS address_id, a.street, a.city " .


"FROM users u INNER JOIN address a ON u.address_id = a.id";

$rsm = new ResultSetMappingBuilder($entityManager);


$rsm->addRootEntityFromClassMetadata('MyProject\User', 'u');
$rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' =>
'address_id'));

Hidratadores

Los hidratadores son los encargados de cargar los objetos de resultados con los datos obtenidos
de la base de datos.

Se puede elegir las siguientes opciones,


SQL a Entities.
SQL a arrays estructurados.
SQL a array de resultados escalares.
SQL a una simple variable de resultado.
La hidratacin es uno de las partes mas complejas de doctrine, estos pueden obtener resultados
de:

Selects a una tabla.


Joins con cardinalidad n:1 o 1:n, agrupando.
Resultados mezclados de valores escalares.
Hidratacin de resultados con valores escalares como clave.
Los modos de hidratacin son:

Query::HYDRATE_OBJECT
HIdrata sobre objetos.
Query::HYDRATE_ARRAY
Hidrata el resultado en un array que representa a los objetos.
Query::HYDRATE_SCALAR
Si se requiere retornar un resultado unico con varios valores en vez de un objeto.
Query::HYDRATE_SINGLE_SCALAR
Si se requiere retornar un solo valor de resultado.
Si bien los hidratadores ya estn definidos, eso no significa que uno no pueda definir uno nuevo.
En el caso de que necesitemos que el resultado cumpla con ciertas reglas, es posible, mediante la
implementacin de ciertas clases, y su posterior configuracin, utilizar un nuevo hidratador.

9
Eventos y Callbacks

La forma mas facil de implementar los eventos, es haciendo uso del ciclo de vida de la entity,
poniendo los eventos en la misma entidad.

Durante la existencia de una entidad, el EntityManager y el UnitOfWork desencadenan un


montn de eventos, estos eventos pueden ser manejados de distintas maneras:
preRemove - El evento preRemove lo produce una determinada entidad antes de ejecutar
la operacin de remocin del respectivo EntityManager de esa entidad. Este no es llamado
por una declaracin DELETE de DQL.
postRemove - El evento postRemove ocurre por una entidad, despus de haber borrado la
entidad. Este se debe invocar despus de las operaciones de eliminacin de la base de
datos. Este no es llamado por una declaracin DELETE de DQL.
prePersist - El evento prePersist se produce por una determinada entidad antes de que el
EntityManager respectivo ejecute las operaciones de persistencia de esa entidad.
postPersist - El evento postPersist ocurre por una entidad, despus de hacer permanente
la entidad. Este se debe invocar despus de las operaciones de insercin de la base de
datos. Genera valores de clave primaria disponibles en el evento postPersist.
preUpdate - El evento preUpdate ocurre antes de que las operaciones de actualizacin de
datos de la entidad pasen a la base de datos. No es llamada por una declaracin UPDATE
de DQL.
postUpdate - El evento postUpdate se produce despus de las operaciones de
actualizacin de datos de la entidad en la base de datos. No es llamada por una
declaracin UPDATE de DQL.
postLoad - El evento postLoad ocurre para una entidad, despus que la entidad se ha
cargado en el EntityManager actual de la base de datos o despus de que la operacin de
actualizacin se ha aplicado a la misma.
loadClassMetadata - El evento loadClassMetadata se produce despus de que los
metadatos de asignacin para una clase se han cargado desde una fuente de asignacin
(anotaciones/xml/yaml).
onFlush - El evento onFlush ocurre despus de computar el conjunto de cambios de todas
las entidades gestionadas. Este evento no es un ciclo de vida de la retrollamada.

10
Estos se pueden conectar a dos diferentes tipos de escuchas de eventos:
El ciclo de vida de las retrollamadas son mtodos de las clases entidad que se llaman
cuando se lanza el evento. Ellos no reciben absolutamente ningn argumento y estn
diseados especficamente para permitir cambios en el interior del estado de las clases
entidad.
Los escuchas del ciclo de vida de eventos son clases con mtodos de retrollamada
especficos que reciben algn tipo de instancia EventArgs que dan acceso a la entidad, al
EntityManager o a otros datos pertinentes.

Para poder usar los del ciclo de retrollamadas, la entity debe tener la anotacion
@HasLifecycleCallbacks.
/** @Entity
* @HasLifecycleCallbacks
*/
class User
{
/** @PostUpdate */
public function postUpdate() {

}
}

Event Listener y Event Suscriber

Existen dos maneras mas optimas de manejar el ciclo de vida de una entidad, es utilizando los
Event Listeners o Event Suscriber, estos mecanismos nos da mayor desacoplamiento de las
entidades, ya que no se definen en ellas, permitiendo escribir comportamientos reutilizables para
distintas clases.
Si bien las dos formas son muy similares en su implementacin los Listeners son mucho mas
simples.

Event Listeners

Como ya hemos visto, doctrine tiene varios eventos que ejecuta durante el ciclo de vida de un a
entity.
Con los event listeners, nosotros le avisamos a doctrine que existe un listener a la espera de algun
evento en particular, en momento que esto sucede se ejecuta la funcionalidad requerida.

11
Listener:

namespace AppBundle\EventListener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity\Persona;

class ModificacionListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();

if (!$entity instanceof Persona) {


return;
}

$entityManager = $args->getEntityManager();
// Realizo las tareas necesarias
}
}
}

Esta es una clase listener que solo escucha el evento Post Persist, pero como se puede ver se
compara especificamente por la clase Persona, esto se debe a que doctrine va a ejecutar este
listener con todas las entity, ya que no hay manera de filtrarlo.

Para que esto funcione debemos agregar el Listener como un servicio de la siguiente manera:
services:
app.listeners.persona:
class: AppBundle\EventListener\ModificacionListener
tags:
- { name: doctrine.event_listener, event: prePersist, method: prePersist }

Con estos datos doctrine va a buscar todos los doctrine.event_listener y ejecutarlos, hay que tener
en cuenta que se pueden agregar cuantos eventos se necesiten en la misma clase, la configuracion
del servicio se tiene que modificar en base a ese requerimiento.

12
Event Suscriber

La forma de trabajo del event suscriber varia un poco a la de listener. En este caso hay que
implementar la clase EventSuscriber, en ella la funcion principal a implmentar es
getSuscribedEvents, en la que debemos retornar a que eventos se suscribe la clase.

Suscriber:

namespace AppBundle\EventListener;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use AppBundle\Entity\Persona;

class ModificacionSubscriber implements EventSubscriber


{
public function getSubscribedEvents(){
return array(
'prePersist',
);
}

public function preUpdate(LifecycleEventArgs $args)


{
$entity = $args->getEntity();

if ($entity instanceof Product) {


$entityManager = $args->getEntityManager();

}
}
}

En esta clase trabajamos de la misma manera, preguntando por la entity con la que queremos
trabajar.

En este caso la configuracion del suscriber varia levemente:


services:

13
app.suscriber.persona:
class: AppBundle\EventListener\ModificacionSubscriber
tags:
- { name: doctrine.event_subscriber, connection: default }
Al ser un suscriber, no le especificamos los eventos a ejecutar, doctrine, ejecutar todos los
devueltos por getSuscribedEvent().

Entity Listener

A partir de la versin 2.4 de doctrine se pueden utilizar los EntityListeners, estos listeners son ms
especficos para cada Entity y se ejecutan solo para la clase dada.

Para poder usarlos se tiene que agregar la anotacion @ORM\EntityListeners({"ClaseListener"}) a la


entity que va a utilizar el listener.
El motor de eventos va a tratar de ejecutar los eventos segun la convencion, o sea prePersit,
postPersit, etc., excepto que se definan con otro nombre y se especifiquen con una anotacion.

class PersonaListener
{
/** @PrePersist */
public function prePersistHandler(Persona $persona, LifecycleEventArgs $event) { // ... }

/** @PostPersist */
public function postPersistHandler(Persona $persona, LifecycleEventArgs $event) { // ... }

/** @PreUpdate */
public function preUpdateHandler(Persona $persona, PreUpdateEventArgs $event) { // ... }

/** @PostUpdate */
public function postUpdateHandler(Persona $persona, LifecycleEventArgs $event) { // ... }

/** @PostRemove */
public function postRemoveHandler(Persona $persona, LifecycleEventArgs $event) { // ... }

/** @PreRemove */
public function preRemoveHandler(Persona $persona, LifecycleEventArgs $event) { // ... }

/** @PreFlush */
public function preFlushHandler(Persona $persona, PreFlushEventArgs $event) { // ... }

14
/** @PostLoad */
public function postLoadHandler(Persona $persona, LifecycleEventArgs $event) { // ... }
}

A partir de la version 2.5 de doctrine se puede llegar a utilizar servicios en symfony para definirlos.

services:
persona_listener:
class: \PersonaListener
tags:
- { name: doctrine.orm.entity_listener }
- { name: doctrine.orm.entity_listener, entity_manager: custom }

15

Das könnte Ihnen auch gefallen