Gestió del model de dades amb Doctrine
Introducció a Doctrine
Què és un ORM?
Un ORM (Object Relational Mapping) és un framework encarregat de tractar amb una base de dades relacional (connectar amb ella, realitzar operacions de consulta, inserció, etc.), de manera que, de cara a l'aplicació, es converteixen a objectes tots els elements que s'extraguen de la base de dades i viceversa (els objectes de l'aplicació es transformen en registres de la base de dades, arribat el cas). D'aquesta forma, l'ORM s'encarregarà de realitzar aquesta conversió o mapatge automàticament per nosaltres. Definint una sèrie de regles, indicarem quines taules de la base de dades relacional es corresponen amb quines classes del nostre model, i quins camps de cada taula es corresponen amb quins atributs de cada classe. A partir d'ací, l'ORM s'encarregarà d'extraure la informació de la base de dades i crear els objectes corresponents, o de convertir els objectes amb els seus atributs en registres de la base de dades, amb les seues corresponents columnes.
El principal avantatge d'utilitzar un ORM com Doctrine és aïllar l'aplicació del gestor de base de dades que hàgem triat (MySQL, Oracle, PostgreSQL...) ja que a nivell d'aplicació treballarem amb objectes, i serà Doctrine qui s'encarregue de connectar amb la base de dades triada, i transformar els objectes per a adaptar-los a la mateixa.
Configuració bàsica de Doctrine
Per a poder utilitzar Doctrine, hem d'indicar com connectar al servidor
de base de dades que anem a utilitzar. Aquests paràmetres de connexió es
poden configurar en l'arxiu .env
del nostre projecte. Aquest és un arxiu
on es defineixen certes variables pròpies d'entorn, que després es
processen i es converteixen en variables reals. En el nostre cas,
definim una variable anomenada DATABASE_URL
, amb una URL on s'especifiquen tant
l'adreça i port de connexió a la base de dades, com el login i password
necessaris per a accedir, així com el nom de la base de dades a la qual
connectar. Per exemple, per a una base de dades MySQL, l'estructura
general serà aquesta:
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name
Tenint en compte l'usuari i contrasenya per defecte de phpMyAdmin per a XAMPP (usuari root i contrasenya buida), aquest podria ser un valor vàlid per a connectar a la nostra base de dades de contactes:
DATABASE_URL=mysql://root@127.0.0.1:3306/movies-symfony
Aquest arxiu .env
pot utilitzar-se tant en desenvolupament com en
producció, encara que en aquest últim cas es recomana definir variables
d'entorn reals en el sistema, per a evitar pèrdua de rendiment en haver
de traduir aquest arxiu per a cada petició.
Gestió dels entorns
Symfony suporta la noció d'entorns. Per defecte, té suport integrat per tres entorns,
però pots afegir-ne tants com vulgues: dev
, prod
i test
. Tots els entorns
comparteixen el mateix codi, però treballen amb configuracions diferents.
Per exemple, totes les eines de depuració estan habilitades en l'entorn dev
.
En l'entorn prod
l'aplicació està optimitzada per al rendiment.
El canvi d'un entorn a un altre pot realitzar-se modificant la variable d'entorn APP_ENV
.
Un arxiu .env
amb uns valors acuradament escollits es genera automàticament quan es crea el projecte:
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=f307a57c2c035484820f08a0494076dc
###< symfony/framework-bundle ###
###> symfony/mailer ###
# MAILER_DSN=smtp://localhost
###< symfony/mailer ###
DATABASE_URL="mysql://root@127.0.0.1:3306/symfony-movies"
Qualsevol paquet pot afegir més variables d'entorn a aquesta imatge gràcies a la seva recepta utilitzada per Symfony Flex.
El fitxer .env
s'envia al repositori i descriu els valors per defecte de l'entorn de producció. Podeu substituir aquests valors creant un fitxer .env.local
. Aquest fitxer no ha de ser enviat a l'repositori i és per això que el fitxer .gitignore
ja l'està ignorant.
Mai guardis dades secretes o confidencials en aquests arxius.
Per a obtenir més informació consulta Configuring Symfony
En el cas que la base de dades encara no existisca, Doctrine pot crear-la per nosaltres. Per a açò, n'hi ha prou amb escriure el següent comando:
php bin/console doctrine:database:create
Created database 'movies-symfony' for connection named default
Creació d'entitats
Les entitats, en Doctrine són objectes PHP que es poden identificar mitjançant un identificador únic o clau primària. Aquestes classes no necessiten estendre cap classe base abstracta o interfície.
Una entitat conté propietats de persitència. Una propietat de persistència és una instància de l'entitat que es desa i es recupera de la base de dades mitjançant les capacitats de mapeig de dades de Doctrine.
En resum, podríem dir que les entitats són les classes que van a compondre el model de dades de la
nostra aplicació. Per exemple, per a la nostra aplicació de pel·licules,
necessitarem una entitat/classe anomenada Movie
que emmagatzeme les
dades concretes de cada pel·lícula:
Per a crear una entitat, emprem el següent comando des del terminal (dins de la carpeta principal del nostre projecte Symfony):
php bin/console make:entity
S'iniciarà un assistent que ens anirà demanant informació per a construir l'entitat:
- Nom de la classe o entitat.
- Propietats o atributs de la classe, per a cadascun, demanarà el nom
(si directament premem
Intro
deixarà de demanar-nos més dades), el tipus de dada, la longitud o grandària del camp, si admet nuls...
Per exemple, per al cas de la nostra classe Movie
, el procés quedaria
així:
php bin/console make:entity
Class name of the entity to create or update (e.g. FiercePuppy):
> Movie
created: src/Entity/Movie.php
created: src/Repository/MovieRepository.php
Entity generated! Now let's add some fields!
You can always add more fields later manually or by re-running this command.
New property name (press <return> to stop adding fields):
> title
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 100
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press <return>
to stop adding fields):
> tagline
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 255
Can this field be null in the database (nullable) (yes/no) [no]:
> yes
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press <return>
to stop adding fields):
> poster
Field type (enter ? to see all types) [string]:
> string
Field length [255]:
> 100
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press <return>
to stop adding fields):
> releaseDate
Field type (enter ? to see all types) [string]:
> date
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press <return>
to stop adding fields):
> overview
Field type (enter ? to see all types) [string]:
> text
Can this field be null in the database (nullable) (yes/no) [no]:
> no
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press <return>
to stop adding fields):
>
Success!
Next: When you're ready, create a migration with php bin/console make:migration
Com a resultat, es generarà una classe Movie
dins de la carpeta
src/Entity
. El codi queda com segueix:
/**
* @ORM\Entity(repositoryClass=MovieRepository::class)
*/
class Movie
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=100)
*/
private $title;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $tagline;
/**
* @ORM\Column(type="string", length=100)
*/
private $poster;
/**
* @ORM\Column(type="text")
*/
private $overview;
/**
* @ORM\Column(type="date")
*/
private $releaseDate;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getTagline(): ?string
{
return $this->tagline;
}
public function setTagline(?string $tagline): self
{
$this->tagline = $tagline;
return $this;
}
public function getPoster(): ?string
{
return $this->poster;
}
public function setPoster(string $poster): self
{
$this->poster = $poster;
return $this;
}
public function getOverview(): ?string
{
return $this->overview;
}
public function setOverview(string $overview): self
{
$this->overview = $overview;
return $this;
}
public function getReleaseDate(): ?\DateTimeInterface
{
return $this->releaseDate;
}
public function setReleaseDate(\DateTimeInterface $releaseDate): self
{
$this->releaseDate = $releaseDate;
return $this;
}
}
id
que usàvem en la nostra base de dades
de prova (el servei creat a tal efecte) l'hem reemplaçat per un camp enter
que es genera automàticament com a clau principal de la classe. Per tant, només hem
hagut d'especificar el títol, l'eslògan, la sinopsi, la data d'estrena i el pòster.
Quant als tipus de dades que podem especificar, si premem ?
i Intro
quan
especifique el tipus de dada, veurem un llistat complet dels
tipus disponibles (també ho podeu consultar ací). L'habitual serà
treballar amb cadenes de text d'una longitud determinada (string),
textos il·limitats (text), enters (integer), booleans (boolean), reals
(float), dates (timestamp o datetime, depenent del que vulguem
emmagatzemar)...
Repositori associat a l'entitat
En crear l'entitat mitjançat l'ordre make:entity
és crea també un repositori
associat, que es vincula mitjançant el paràmetre repositoryClass
de la anotació
@ORM\Entity
.
/**
* @ORM\Entity(repositoryClass=MovieRepository::class)
* /
Generació de l'esquema
Una vegada hem definit l'entitat, podem generar la corresponent taula en la base de dades. Per a açò, escrivim aquest comando:
php bin/console make:migration
El que fa aquest comando és aplicar els canvis entre el nostre model
d'entitats i l'esquema de la base de dades, i generar un arxiu PHP que
s'encarregarà de bolcar aqueixos canvis a la base de dades. Per consola
se'ns informarà d'on està aquest arxiu perquè ho comprovem (estarà en
la carpeta src/Migrations
), i si tot és correcte, executant aquest altre
comando es reflectiran els canvis en la base de dades:
php bin/console doctrine:migration:migrate
Com podem comprovar, cada atribut de la nostra entitat Movie
es
correspon amb un camp del mateix nom en la taula movie
.
Convé esmentar també que aquest últim comando executarà tots els arxius de migració que encara no s'hagen migrat efectivament a la base de dades, per la qual cosa si hi ha algun que no vulguem migrar (perquè s'haja generat equivocadament, per exemple), haurem d'eliminar-la abans.
Editar entitats
Què passa si, després de crear una entitat, volem modificar la seua
estructura? Podem editar la classe de l'entitat manualment per a
afegir, modificar o esborrar camps, però també podem tornar a executar
el comando make:entity
, indicar el mateix nom de classe que volem
modificar, i especificar els nous camps que vulguem afegir (en el cas
que el que vulguem siga afegir camps).
Després de definir els canvis en la(s) entitat(s) desitjada(es), haurem de generar una nova migració amb els comandos vistos en el subapartat anterior.
Establir altres claus primàries
Per defecte, hem vist que Doctrine agrega un camp id
a les entitats,
que és autonuméric i actua com a clau primària. En el cas que no
vulguem que siga així, i preferim triar un altre camp no autonumèric
com a clau primària, hem de seguir aquests passos:
- Eliminar l'atribut
id
i el seugetter
corresponent de l'entitat. - Afegir la següent anotació a l'atribut que hàgem triat com a clau primària:
/**
* @ORM\Id()
* ...
*/
private $fieldName
Operacions contra la base de dades
Ara que ja hem vist com definir entitats simples, vegem com fer operacions amb elles, tals com a insercions, esborrats, modificacions i consultes. Per a realitzar aquestes operacions, ens valdrem d'un objecte molt important en Doctrine, el seu entity manager, a través del que farem les insercions, esborrats, etc. També utilitzarem el repositori de l'entitat corresponent, per a realitzar les cerques.
Inserir objectes
Si volem afegir objectes nous a la nostra base de dades, n'hi ha prou
que creem un objecte de l'entitat corresponent en el mètode oportú, i
cridem al mètode persist
i flush
de l'entity manager de Doctrine.
Per exemple, per a provar, crearem un controlador en la nostra
classe MovieController
associat a una ruta /movies/create
, que de
moment serà de proves fins que fem un formulari d'inserció. Dins
d'aquest mètode, crearem un objecte Movie
amb dades prefixades,
obtenim l'entity manager de Doctrine i persistim l'objecte:
/**
* @Route("/movies/create", name="movies_create")
*/
public function create()
{
$entityManager = $this->getDoctrine()->getManager();
$movie = new Movie();
$movie->setTitle("Ava");
$movie->setOverview("A black ops assassin is forced to fight for her
own survival after a job goes dangerously wrong.");
$movie->setReleaseDate(new DateTime("2020-09-25"));
$movie->setPoster("noposter.jpg");
...
$entityManager->persist($movie);
$entityManager->flush();
return new Response("The movie with id " . $movie->getId().
" has been inserted successfully!" );
}
Si accedim des del navegador a movies-symfony/movies/create
podrem veure el resultat en la taula movie
de la nostra base de dades:
És important recalcar que la cridada persist
per si sola no actualitza
la base de dades, sinó que indica que es vol persistir l'objecte
indicat. És la crida a flush
la que fa efectiva aqueixa persistència.
Detectant errors en la inserció
Si ocorre algun error en la inserció (per exemple, perquè algun camp és
nul i no puga ser-ho, o perquè es duplica la clau primària), es
provocarà una excepció en cridar al mètode flush
. Podem tractar aquest
error simplement capturant l'excepció i generant la resposta apropiada:
$entityManager->persist($object);
try
{
$entityManager->flush();
return new Response("Object inserted");
} catch (Exception $e) {
return new Response("Error inserting objects");
}
Obtenir objectes
A l'hora d'obtenir objectes d'una taula, existeixen diferents mètodes que podem emprar. Per exemple:
- El mètode
find
localitza l'objecte per la clau primària (normalment l'id
) que se li passa com a paràmetre. Així cercaríem la pel·lícula amb id 1:$movie = $repositori->find(1)
; - El mètode
findOneBy
localitza un objecte que complisca els criteris de cerca passats com a paràmetre. Així cercaríem la pel·lícula el títol del qual siga "Ava":En el cas de voler definir més criteris de cerca, es passarien un darrere$movie = $repositori->findOneBy(["title" => "Ava"]);
l'altre en l'array, separats per comes. - El mètode
findBy
localitza tots els objectes que complisquen els criteris de cerca passats com a paràmetre. Aquesta instrucció és com l'anterior, però retorna un array de pel·lícules amb tots els resultats coincidents (en el cas que hi haja un sol resultat, retorna un array d'un element):$movies = $repositori->findBy(["title" => "Ava"]);
- El mètode
findAll
(sense paràmetres), obté tots els objectes de la col·lecció.$movies = $repositori->findAll();
Tots aquests mètodes s'obtenen a partir d'un repositori de la classe, que ve a ser alguna cosa així com un assistent que ens ajuda a obtenir objectes que pertanguen a aqueixa classe.
Vegem un exemple amb la nostra classe MovieController
, modificarem
el nostre mètode show
perquè, en lloc de cercar en la base de
dades de prova que hem vingut emprant en sessions anteriors, cerque per
id
en la base de dades real. Per a açò, obtenim el repositori de la nostra
classe Movie
i cerquem (find)
la pel·lícua amb l'identificador que hem rebut com a paràmetre:
/**
* @Route("/movies/{id}", name="movies_show", requirements={"id"="\d+"})
*/
public function show(int $id)
{
$movieRepository = $this->getDoctrine()->getRepository(Movie::class);
$movie = $movieRepository->find($id);
if ($movie)
{
return $this->render('movies_show.html.twig', ["movie"=>$movie]
);
}
else
return $this->render('movies_show.html.twig', [
'movie' => null]
);
}
Consultes més avançades
Amb els mètodes de consulta anteriors podem realitzar consultes que es limiten a comprovar si un o diversos camps d'un objecte són iguals a uns criteris de cerca determinats. Però, com podríem, per exemple, cercar les pel·lícules el títol dels quals continga un cert text, o les pel·licules estrenades en un període determinat? Per a aquest tipus de consultes, necessitem ampliar el repositori de la nostra entitat.
Per exemple, per a la nostra entitat Movie
, imaginem que volem cercar
les pel·lícules el títol dels quals coincideix en un cert text. Per a aconseguir
açò, necessitem editar el repositori de l'entitat, que està en
src/Repository/MovieRepository.php
. Aquest arxiu conté comentats un
parell de mètodes de prova que podríem definir per a ampliar les
capacitats de l'entitat.
En el nostre cas, afegirem un mètode que s'encarregarà d'obtenir les pel·lícules el títol o la sinopsi dels quals continga un text determinat que li passem com a paràmetre:
/**
* @return Movie[] Returns an array of Movie objects
*/
public function filterByText(string $text): array
{
$qb = $this->createQueryBuilder('m')
->orWhere('m.title LIKE :value')
->orWhere('m.overview LIKE :value');
$qb->setParameter('value', "%".$text."%");
$qb->orderBy('m.title', 'ASC');
$query = $qb->getQuery();
return $query->getResult();
}
m
(de Movie
) que usarem per a referenciar les propietats de
les pel·lícules, per exemple, en la clàusula where
. El que ve a fer aquest codi és cercar aquelles
pel·lícules m
el títol de les quals coincideixca amb el paràmetre text
, i a
continuació específica que aquest paràmetre text
és igual al paràmetre
que rebem en el mètode, tancat entre símbols '%', per a indicar que és
igual el que hi haja davant o darrere del text.
Ara, ja podríem utilitzar aquest mètode des d'on ho necessitem. Per
exemple, podem modificar el mètode filter
de MovieController
perquè
cerque pel·lícules per títol emprant aquest nou mètode:
/**
* @Route("/movies/{text}", name="movies_filter")
*/
public function filter(string $text)
{
$movieRepository = $this->getDoctrine()->getRepository(Movie::class);
$movies = $movieRepository->findByTitle($text);
return $this->render('movies_filter.html.twig', array(
'movies' => $movies
));
}
$qb = $this->createQueryBuilder('p')
->andWhere('p.edat > :edat')
->setParameter('edat', $edat)
->getQuery();
Alternativament, també podem emprar un llenguatge anomenat DQL (Doctrine Query Language) per a realitzar la consulta anterior:
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery('SELECT m FROM App\Movie m WHERE m.title LIKE
:text' )->setParameter('text', '%'. $text. '%');
return $query->execute();
I, com a tercera via, també podem emprar SQL estàndard, però en aquest cas el que obtindríem ja no seria un array d'objectes, sinó un array de registres, com si empràrem la llibreria PDO de PHP per a accedir a la base de dades.
Ací teniu enllaços per a consultar informació addicional tant de Query Builder com del llenguatge DQL.
Actualitzar i esborrar objectes
Actualitzar objectes
Per a actualitzar un objecte en una base de dades, hem de seguir tres passos:
- Obtenir l'objecte de la base de dades (típicament fent un find per la seua clau primària)
- Modificar les dades necessàries amb els respectius setters de l'objecte
- Fer un flush per a actualitzar els canvis en la base de dades.
Si, per exemple, volguérem actualitzar les dades de la pel·lícula amb id
=
1, faríem açò:
$entityManager = $this->getDoctrine()->getManager();
$repository = $this->getDoctrine()->getRepository(Movie::class);
$movie = $repository->find(1);
if ($movie) {
$movie->setTitle("Updated movie");
$entityManager->flush();
}
Esborrar objectes
L'esborrat d'objectes és similar a l'actualització: hem d'obtenir
l'objecte també, però després cridem al mètode remove
per a
esborrar-lo, i finalment a flush
. Així esborraríem la pel·lícula amb
id 1
$entityManager =$this->getDoctrine()->getManager();
$repository = $this->getDoctrine()->getRepository(Movie::class);
$movie = $repository->find(1);
if ($movie) {
$entityManager->remove($movie);
$entityManager->flush();
}
flush
pot provocar una excepció si l'operació no ha pogut dur-se a terme. Hem
de tenir-ho en compte per a capturar-la i generar la resposta oportuna.
Relacions entre entitats
Fins ara les operacions que hem fet s'han centrat en una única taula o entitat (l'entitat/taula de pel·lícules en l'exemple de les anotacions). Vegem ara com podem treballar amb més d'una taula/entitat que estiguen relacionades entre sí.
Existeixen dos tipus principals de relacions entre entitats:
- Molts a un: en aquest tipus s'englobarien les relacions "un a molts", "molts a un" i "un a un", ja que en qualsevol dels tres casos, la relació es reflecteix afegint una clau ajeno en una de les dues entitats que referencie a l'altra.
- Molts a molts: en aquest tipus de relacions, es necessita d'una taula addicional per a reflectir la relació entre les entitats.
Anem a definir una relació molts a un en la nostra base de dades de
pel·lícules. Per a açò, anem a crear primer una entitat anomenada
Genre
, que només continga un id autoincremental i un nom (string):
php bin/console make:entity
Class name of the entity to create or update (i.g. AgreeableGnome):
>Genre
created: src/Entity/Genre.php
created: src/Repository/GenreRepository.php
New property name (press to stop adding fields):
>name
Field type (enter? to see all types) [string]:
>string
Field length [255]:
>255
Can this field be null in the database (nullable) (yes/no) [no]:
>no
updated: src/Entity/Genre.php
Add another property? Enter the property name (or press to stop adding
fields):
Success!
php bin/console make:migration
php bin/console doctrine:migration:migrate
Ara, anem a fer que les pel·lícules tinguen un gènere associat. Per a
açò, editem l'entitat Movie
i li afegim un nou camp, anomenat
gènere, que serà de tipus relació molts a un (una pel·lícula pertanyerà
a una categoria, i un gènere pot tenir moltes pel·lícules).
php bin/console make:entity
Class name of the entity to create or update (i.g. DeliciousPuppy):
> Movie
Your entity already exists! So let's add some new fields!
New property name (press to stop adding fields):
>genre
Field type (enter? to see all types) \[string\]:
>relation
What class should this entity be related to?:
>Genre
What type of relationship is this?
---------------------------------------------------------------------
Type Description
---------------------------------------------------------------------
ManyToOne Each Movie relates to (has) one Genre.
Each Genre can relate/has to (have) many Movie objects
OneToMany Each Movie can relate to (have) many Genre objects.
Each Genre relates to (has) one Movie
ManyToMany Each Movie can relate to (have) many Genre objects.
Each Província can also relate to (have) many Contacte objects
OneToOne Each Movie relates to (has) exactly one Genre.
Each Genre also relates to (has) exactly one Movie.
Relation type? [ManyToOne, OneToMany, ManyToMany, OneToOne]:
> ManyToOne
Is the Movie.genre property allowed to be null (nullable)?
(yes/no) [yes]:
> no
Do you want to add a new property to Genre so that you can
access/update Movie objects from it - i.g. $genre-\>getMovies()? (yes/no) [yes]:
>no
updated: src/Entity/Movie.php
Add another property? Enter the property name (or press to stop adding
fields):
Success!
Com pot veure's, a l'hora de triar el tipus de camp, indiquem que és una relació (relation), en aquest cas ens demana indicar a quina entitat està vinculada (Genre, en aquest cas), i quin tipus de relació és (ManyToOne en el nostre cas, però podem triar qualsevol de les altres tres opcions OneToMany, OneToOne o ManyToMany). També podem comprovar que l'assistent ens pregunta si volem afegir un camp en l'altra entitat perquè la relació siga bidireccional (és a dir, perquè des d'un objecte de qualsevol de les dues entitats puguem consultar el/els objecte(s) associat(s) de l'altra. En aquest cas indiquem que no per a simplificar el codi.
Important
Cal tenir en compte que si la taula movie
ja té pel·lícules, i a l'hora
de crear la clau aliena decidim que no puga tenir valors nuls tindrem
problemes, ja que fallarà la integritat referencial.
Hi ha diverses solucions com crear un gènere amb id 0 o indicar que
la clau aliena genre
sí pot contenir valors nuls. I després, canviar-ho.
Després d'aquests canvis, realitzem de nou la migració, i ja tindrem el
nou camp afegit en la nostra entitat Movie
i a la taula movie
de
la base de dades:
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Genre")
* @ORM\JoinColumn(nullable=false)
*/
Treballar amb entitats relacionades
Ara que ja sabem relacionar entitats entre sí. Com podem inserir una entitat que depèn d'una altra, o accedir a les dades d'una entitat des de l'altra?
Inserció d'entitats relacionades
Per exemple, si volguérem inserir un pel·lícula assignant-li un gènere:
-
Si el gènere no existeix, crearem un objecte de tipus Genre, i després un altre de tipus Movie, establint com a
genre
l'objecteGenre
recentment creat:$entityManager = $this->getDoctrine()->getManager(); $genre = new Genre(); $genre->setName("Western"); $movie = new Movie(); $movie->setTitle("A test title"); $movie->setTagline("A test tagline"); $movie->setReleaseDate(new \DateTime()); $movie->setPoster->setPoster("noposter.jpg"); $movie->setOverview->setOverview("A test overview"); $movie->setGenre($genre); $entityManager->persist($genre); $entityManager->persist($movie); $entityManager->flush();
-
Si el gènere sí existeix, el recuperem de la base de dades (amb algun mètode
find
o similar) i després crearem crear l'objecte Movie i li assignem aqueix objecte Genre:$entityManager = $this->getDoctrine()->getManager(); $repositori = $this->getDoctrine()->getRepository(Genre::class); $genre = $repository->find(1); $movie = new Movie(); $movie->setTitle("A test title"); $movie->setTagline("A test tagline"); $movie->setReleaseDate(new \DateTime()); $movie->setPoster->setPoster("noposter.jpg"); $movie->setOverview->setOverview("A test overview"); $movie->setGenre($genre); $entityManager->persist($genre); $entityManager->persist($movie); $entityManager->flush();
Obtenir entitats relacionades
En el cas que recuperem una entitat que està relacionada amb una altra, l'accés a aqueixa altra entitat és immediat des de la primera. Per exemple, si volguérem saber el nom del gènere d'una pel·lícula amb id 1, faríem alguna cosa així:
$repositori = $this->getDoctrine()->getRepository(Movie::class);
$movie = $repositori->find(1);
$genreName = $movie->getGenre()->getName();
En qualsevol cas, és important recalcar que Doctrine no recupera les dades de l'entitat relacionada (la província, en aquest cas), fins que es demanen efectivament (és a dir, fins que no preguntem pel nom del genre, Doctrine no tracta d'obtenir l'objecte Genre complet). Aquesta estratègia de recuperació d'entitats relacionades s'anomena lazy loading o càrrega diferida.