Beruflich Dokumente
Kultur Dokumente
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.
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;
}
/**
* @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;
}
/**
* @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;
}
/**
* @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.
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;
Hidratadores
Los hidratadores son los encargados de cargar los objetos de resultados con los datos obtenidos
de la base de datos.
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.
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() {
}
}
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();
$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;
}
}
}
En esta clase trabajamos de la misma manera, preguntando por la entity con la que queremos
trabajar.
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.
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