Abbiamo visto finora come accedere e aggiornare i dati contenuti all'interno di una tabella MySQL utilizzando Doctrine. In questa lezione, invece, vedremo come eseguire query complesse e, soprattutto, gestire relazioni tra più tabelle.
Query complesse attravero DQL
Per effettuare query più complesse abbiamo a disposizione due strumenti di Doctrine: Query Builder e DQL (Doctrine Query Language).
Query Builder
Supponiamo di dover recuperare tutti i libri con un prezzo maggiore di 20 euro. Query Builder di Doctrine ci mette a disposizione dei metodi molto intuitivi per raggiungere il nostro scopo:
$repository = $this->getDoctrine()
->getRepository('AcmeDemoBundle:Book');
$query = $repository->createQueryBuilder('p')
->where('p.price > :price')
->setParameter('price', '22')
->orderBy('p.price', 'ASC')
->getQuery();
$books = $query->getResult();
Come possiamo notare abbiamo richiamato il Query Builder attraverso il metodo createQueryBuilder('p')
- il parametro indica l'alias. Il resto è piuttosto semplice, l'unico elemento a cui si dovrà prestare attenzione è il metodo setParameter
; quest'ultimo è consigliabile per passare i parametri della query così da prevenire attacchi basati sulla SQL Injection.
DQL
DQL (Doctrine Query Language) è uno strumento che consente di eseguire query complesse in un linguaggio molto vicino al classico SQL, per cui l'esempio che visto in precedenza potrebbe essere riscritto nel modo seguente:
$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
'SELECT p
FROM AcmeDemoBundle:Book p
WHERE p.price > :price
ORDER BY p.price ASC'
)->setParameter('price', '22');
$books = $query->getResult();
Tale istruzione è molto simile ad una comune query in linguaggio SQL, quello che cambia è come vengono trattati i parametri (vediamo i placeholder preceduti da :
) e le tabelle che vengono sostituite dal nome dell'entità definita in Symfony.
Relazioni tra più tabelle
Riprendiamo la struttura della tabella utilizzata per la nostra entità:
Il campo author
in realtà, se utilizzato come semplice testo è poco funzionale. In un progetto reale l'autore è sicuramente un'entità a parte con sue informazioni (nome, cognome, ecc) e, soprattutto, possono esserci più autori per un libro e viceversa. È bene, quindi, ristrutturare il nostro database affinché vengano gestiti gli autori come entità separate e ci sia una relazione tra libro e autore.
La prima operazione da compiere sarà quindi quella di creare l'entità attraverso la console:
php app/console doctrine:generate:entity --entity="AcmeDemoBundle:Author" --fields="firstname:string(255), lastname:string(255)"
Doctrine ci restituirà una entità simile alla seguente:
namespace Acme\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Author
*
* @ORM\Table()
* @ORM\Entity
*/
class Author
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="firstname", type="string", length=255)
*/
private $firstname;
/**
* @var string
*
* @ORM\Column(name="lastname", type="string", length=255)
*/
private $lastname;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set firstname
*
* @param string $firstname
* @return Author
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
return $this;
}
/**
* Get firstname
*
* @return string
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* Set lastname
*
* @param string $lastname
* @return Author
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
return $this;
}
/**
* Get lastname
*
* @return string
*/
public function getLastname()
{
return $this->lastname;
}
}
Ora aggiungiamo una piccola modifica alla classe con cui diciamo a Doctrine che c'è una relazione con l'entità Book
:
use Doctrine\Common\Collections\ArrayCollection;
/**
* Author
*
* @ORM\Table(name="author")
* @ORM\Entity
*/
class Author
{
/* ... */
/**
* @ORM\ManyToMany(targetEntity="Book", mappedBy="author")
*/
protected $books;
public function __construct()
{
$this->books = new ArrayCollection();
}
/* ... */
In sostanza, attraverso le annotations abbiamo detto che avremo una relazione ManyToMany con l'entità Book
. All'interno del costruttore, invece, definiamo la proprietà books
come un'ArrayCollection
che, in realtà, è un tipo di collection di Doctrine che può essere vista come un array semplice con qualche funzione in più.
Aggiorniamo poi l'entità Book
in maniera da adattarla alle modifiche. Per prima cosa rimuoviamo il campo author
e relativi setter/getter e poi inseriamo il seguente codice:
/**
* @ORM\ManyToMany(targetEntity="Author", inversedBy="book")
* @ORM\JoinTable(name="books_authors")
*/
protected $authors;
public function __construct()
{
$this->authors = new ArrayCollection();
}
Nelle annotation abbiamo definito una tabella di join che conterrà le relazioni tra le due entità (books_authors
). Vedremo tra poco che non abbiamo bisogno di creare manualmente la tabella ma tramite questa annotation sarà Doctrine a gestirla automaticamente.
A questo punto, attraverso la console, comunichiamo a Doctrine di aggiornare i metodi setter/getter e di aggiornare la struttura del database:
php app/console doctrine:generate:entities Acme
php app/console doctrine:schema:update --force
Se accediamo ora all'interno del nostro database possiamo vedere che la sua struttura sarà la seguente:
La tabella book
è diventata:
Possiamo notare come sia scomparso il campo author
. La tabella di join invece ha la seguente struttura:
Ora che le due tabelle sono in relazione, vediamo come, ad esempio, salvare e recuperare informazioni.
Per creare un libro, a questo punto, abbiamo bisogno del seguente codice:
$author = new Author();
$author->setFirstname('John Ronald Reuel');
$author->setLastname('Tolkien');
$book = new Book();
$book->setTitle("Il Signore degli Anelli");
$book->setIsbn("88-452-9005-0");
$book->setPrice("22.00");
$book->setDescription("Un mondo sul ciglio dell'abisso, un pugno di eroi capaci di opporsi al male. Una pietra miliare della letteratura di tutti i tempi.");
$book->addAuthor($author);
$author->addBook($book);
$em = $this->getDoctrine()->getManager();
$em->persist($author);
$em->persist($book);
$em->flush();
Come possiamo notare, non cambia moltissimo da quanto abbiamo visto finora. Creiamo l'autore e il libro e, prima di rendere persistenti gli oggetti nel database, aggiungiamo le relazioni attraverso il metodo add
.
Anche il recupero delle informazioni dal database resta quasi invariato:
$em = $this->getDoctrine()->getManager();
$book = $em->getRepository('AcmeDemoBundle:Book')->find($id);
$authors = $book->getAuthors();
//restituisco un array di autori, ma potrei anche ciclarli
//o passarli alla view
var_dump($authors->toArray());
Per approfondire ulteriormente le varie tipologie di relazioni si può consultare la documentazione di Doctrine al seguente link.
Termina con questa lezione la sezione dedicata alle operazioni con le basi di dati. Nella prossima lezione verrà affrontato il discorso riguardante la gestione dei form in Symfony 2.