La théorie
Pour effectuer nos propres méthodes, il faut bien comprendre comment fonctionne Doctrine2 pour effectuer ses requêtes. Il faut notamment distinguer 3 types d'objets qui vont nous servir, et qu'il ne faut pas confondre : le QueryBuilder, la Query et les résultats.
Le QueryBuilder
On l'a déjà vu rapidement, le QueryBuilder permet de construire une Query, mais il n'est pas une Query !
Pour récupérer un QueryBuilder, on peut utiliser simplement l'EntityManager. En effet, il dispose d'une méthode createQueryBuilder() qui nous retournera une instance de QueryBuilder. L'EntityManager est accessible depuis un repository en utilisant l'attribut "_em" d'un repository, soit
<?php $this->_em. Le code complet pour récupérer un QueryBuilder neuf depuis une méthode d'un repository est donc
<?php $this->_em->createQueryBuilder().
Cependant cette méthode nous retourne un QueryBuilder vide, c'est-à-dire sans rien de prédéfini. C'est dommage, car lorsqu'on récupère un QueryBuilder depuis un repository, c'est que l'on veut faire une requête sur l'entité gérée par ce repository. Donc si l'on pouvait définir la partie "
SELECT article FROM SdzBlogBundle:Article" sans trop d'effort, ça serait bien pratique car ce qui est intéressant c'est le reste de la requête. Heureusement, le repository contient également une méthode createQueryBuilder($alias) qui utilise la méthode de l'EntityManager, mais en définissant pour nous le SELECT et le FROM. Vous pouvez jeter un oeil à
cette méthode createQueryBuilder() pour comprendre.
L'alias en argument de la méthode est le raccourci que l'on donne à l'entité du repository. On utilise souvent la première lettre du nom de l'entité, dans notre exemple de l'Article cela serait donc un "a".
Beaucoup de théorie, passons donc à la pratique ! Pour bien comprendre la différence QueryBuilder / Query, ainsi que la récupération du QueryBuilder, rien de mieux qu'un exemple. Nous allons recréer la méthode findAll() dans notre repository Article :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | <?php
// src/Sdz/BlogBundle/Entity/ArticleRepository
namespace Sdz\BlogBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* ArticleRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ArticleRepository extends EntityRepository
{
public function myFindAll()
{
$queryBuilder = $this->createQueryBuilder('a');
// Méthode équivalente, mais plus longue :
$queryBuilder = $this->_em->createQueryBuilder()
->select('a')
->from($this->_entityName, 'a'); // Dans un repository, $this->_entityName est le namespace de l'entité gérée
// Ici, il vaut donc Sdz\BlogBundle\Entity\Article
// On a fini de construire notre requête.
// On récupère la Query à partir du QueryBuilder
$query = $queryBuilder->getQuery();
// On récupère les résultats à partir de la Query
$resultats = $query->getResult();
// On retourne ces résultats
return $resultats;
}
}
|
Cette méthode myFindAll() retourne exactement le même résultat qu'un findAll(), c'est-à-dire un ArrayCollection de toutes les entités Article dans notre base de données. Vous pouvez le voir, faire une simple requête est très facile. Pour mieux le visualiser, je vous propose la même méthode sans les commentaires et en raccourcie :
Code : PHP | <?php
public function myFindAll()
{
return $this->createQueryBuilder('a')
->getQuery()
->getResult();
}
|
Simplissime non ?
Et bien sûr pour récupérer les résultats depuis un contrôleur, le code est le suivant :
Code : PHP | <?php
public function testAction()
{
$liste_articles = $this->getDoctrine()
->getEntityManager()
->getRepository('SdzBlogBundle:Article')
->myFindAll();
// Reste de la méthode du contrôleur.
}
|
Sauf que pour l'instant on a juste récupéré le QueryBuilder, on n'a pas encore joué avec lui. Il dispose de plusieurs méthodes afin de construire notre requête. Il y a une ou plusieurs méthode(s) par partie de requête : le WHERE, le ORDER BY, le FROM, etc. Elles n'ont rien de compliqué, voyez-le dans les exemples suivants.
Commençons par une méthode équivalente au find($id) de base, pour nous permettre de manipuler le where() et le setParameter().
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | <?php
// Dans un repository
public function myFindOne($id)
{
// On passe par le QueryBuilder vide de l'EntityManager pour l'exemple
$qb = $this->_em->createQueryBuilder();
$qb->select('a')
->from('SdzBlogBundle:Article', 'a')
->where('a.id = :id')
->setParameter('id', $id);
return $qb->getQuery()
->getResult();
}
|
Vous connaissez le principe des paramètres, qui est le même qu'avec PDO. On définit un paramètre dans la requête avec ":nom_du_parametre", puis on attribut une valeur à ce paramètre avec la méthode setParameter('nom_du_parametre', $valeur).
Voici un autre exemple pour utiliser le andWhere() ainsi que le orderBy(). Créons une méthode pour récupérer tous les articles écrits par un auteur avant une année donnée :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | <?php
// Depuis un repository
public function findByAuteurAndDate($auteur, $annee)
{
// On utilise le QueryBuilder créé par le repository directement pour gagner du temps
// Plus besoin de faire le select() ni le from() par la suite donc
$qb = $this->createQueryBuilder('a');
$qb->where('a.auteur = :auteur')
->setParameter('auteur', $auteur)
->andWhere('a.date < :annee')
->setParameter('annee', $annee)
->orderBy('a.date', 'DESC');
return $qb->getQuery()
->getResult();
}
|
Maintenant voyons un des avantages du QueryBuilder. Vous vous en souvenez, je vous avais parlé d'une méthode pour centraliser une condition par exemple. Voyons donc une application de ce principe, en considérant que la condition "
articles postés durant l'année en cours" est une condition dont on va se resservir souvent. Il faut donc en faire une méthode, que voici :
Code : PHP | <?php
// Depuis un repository
public function whereCurrentYear(Doctrine\ORM\QueryBuilder $qb)
{
$qb->andWhere('a.date BETWEEN :debut AND :fin')
->setParameter('debut', new \Datetime(date('Y').'-01-01')) // Date entre le 1er janvier de cette année
->setParameter('fin', new \Datetime(date('Y').'-12-31')); // Et le 31 décembre de cette année
return $qb;
}
|
Vous notez donc que cette méthode ne traite pas une Query, mais bien uniquement le QueryBuilder. C'est en ça qu'il est très pratique, car faire cette méthode sur une requête en texte simple est possible, mais compliqué. Il aurait fallu voir si le "WHERE" était déjà présent dans la requête, si oui mettre un "AND" au bon endroit, etc. Bref, pas simple.
Pour utiliser cette méthode, voici la démarche :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | <?php
// Depuis un repository
public function myFind()
{
$qb = $this->createQueryBuilder('a');
// On peut rajouter ce qu'on veut avant
$qb->where('a.auteur = :auteur')
->setParameter('auteur', 'winzou');
// On applique notre condition
$qb = $this->whereCurrentYear($qb);
// On peut rajouter ce qu'on veut après
$qb->orderBy('a.date', 'DESC');
return $qb->getQuery()
->getResult();
}
|
Voilà, vous pouvez dorénavant appliquer cette condition à n'importe laquelle de vos requêtes en construction.
Je ne vous ai pas listé toutes les méthodes du QueryBuilder, il en existe bien d'autres. Pour cela, vous devez absolument mettre la page suivante dans vos favoris :
http://www.doctrine-project.org/docs/o [...] -builder.html Ouvrez-la et gardez-la sous la main à chaque fois que vous voulez faire une requête à l'aide du QueryBuilder, c'est la référence !
La Query
Vous l'avez vu, la Query est l'objet à partir duquel on extrait les résultats. Il n'y a pas grand chose à savoir sur cet objet en lui-même, car il ne permet pas grand chose à part récupérer les résultats. Il sert en fait surtout à la gestion du cache des requêtes. Un prochain chapitre est à venir sur ce cache de requêtes.
Mais détaillons tout de même les différentes façon d'extraire les résultats de la requêtes. Ces différentes manières sont toutes à maîtriser, car elles concernent chacune un type de requête.
| Méthode |
Explications |
Exemple |
| getResult() |
Exécute la requête et retourne un tableau contenant les résultats sous forme d'objet. Vous récupérez ainsi une liste des objets, sur lequels vous pouvez faire des opérations, des modifications, etc.
Même si la requête ne retourne qu'un seul résultat, cette méthode retourne un tableau. |
Code : PHP | <?php
$entites = $qb->getQuery()->getResult();
foreach($entites as $entite)
{
// $entite est une instance d'Article pour notre exemple
$entite->getAttribut();
}
|
|
| getArrayResult() |
Exécute la requête et retourne un tableau contenant les résultats sous forme de tableau.
Comme avec getResult(), vous récupérez un tableau même s'il n'y a qu'un seul résultat.
Mais dans ce tableau, vous n'avez pas vos objets d'origine, vous avez des simples tableaux. Cette méthode est utilisée lorsque vous ne voulez que lire vos résultats, sans y apporter de modification. Elle est dans ce cas plus rapide que son homologue getResult(). |
Code : PHP | <?php
$entites = $qb->getQuery()->getArrayResult();
foreach($entites as $entite)
{
// $entite est un tableau
// Faire $entite->getAttribut() est impossible. Vous devez faire :
$entite['attribut'];
}
|
Heureusement Twig est intelligent : {{ entite.attribut }} exécute $entite->getAttribut() si $entite est un objet, et exécute $entite['attribut'] sinon. Du point de vue de Twig, vous pouvez utiliser getResult() ou getArrayResult() indifféremment. |
| getScalarResult() |
Exécute la requête et retourne un tableau contenant les résultats sous forme de valeur.
Comme avec getResult(), vous récupérez un tableau même s'il n'y a qu'un seul résultat.
Mais dans ce tableau, un résultat est une valeur, non un tableau de valeur (getArrayResult) ou un objet de valeur (getResult). Cette méthode est donc utilisée lorsque vous ne sélectionnez qu'une seule valeur dans la requête, par exemple : SELECT COUNT(*) FROM .... Ici, la valeur est la valeur du COUNT. |
Code : PHP | <?php
$entites = $qb->getQuery()->getScalarResult();
foreach($entites as $valeur)
{
// $valeur est la valeur de ce qui a été sélectionné : un nombre, un texte, etc.
$valeur;
// Faire $valeur->getAttribut() ou $valeur['attribut'] est impossible.
}
|
|
| getOneOrNullResult() |
Exécute la requête et retourne un seul résultat, ou null si pas de résultat. Cette méthode retourne donc une instance de l'entité (ou null) et non un tableau d'entités comme getResult().
Cette méthode déclenche une exception Doctrine\ORM\NonUniqueResultException si la requête retourne plus d'un seul résultat. Il faut donc l'utiliser si l'une de vos requêtes n'est pas censée retourner plus d'un résultat : déclencher une erreur plutôt que de laisser courir permet d'anticiper des futurs bugs ! |
Code : PHP | <?php
$entite = $qb->getQuery()->getOneOrNullResult();
// $entite est une instance d'Article dans notre exemple
// ou null si la requête ne contient pas de résultat
// Et une exception a été déclenchée si plus d'un résultat
|
|
| getSingleResult() |
Exécute la requête et retourne un seul résultat.
Cette méthode est exactement la même que getOneOrNullResult(), sauf qu'elle déclenche une exception Doctrine\ORM\NoResultException si aucun résultat.
C'est une méthode très utilisée, car faire des requêtes qui ne retourne qu'un unique résultat est très fréquent. |
Code : PHP | <?php
$entite = $qb->getQuery()->getSingleResult();
// $entite est une instance d'Article dans notre exemple
// Une exception a été déclenchée si plus d'un résultat
// Une exception a été déclenchée si pas de résultat
|
|
| getSingleScalarResult() |
Exécute la requête et retourne une seule valeur, et déclenche des exception si pas de résultat ou plus d'un résultat.
Cette méthode est très utilisée également pour des requêtes du type SELECT COUNT(*) FROM Article, qui ne retourne qu'une seule ligne de résutlat, et une seule valeur dans cette ligne. |
Code : PHP | <?php
$valeur = $qb->getQuery()->getSingleScalarResult();
// $valeur est directement la valeur du COUNT dans la requête exemple.
// Une exception a été déclenchée si plus d'un résultat
// Une exception a été déclenchée si pas de résultat
|
|
| execute() |
Exécute la requête.
Cette méthode est utilisée principalement pour exécuter des requêtes qui ne retournent pas de résultats (des UPDATE, INSERT INTO, etc.).
Cependant, toutes les autres méthodes que nous venons de voir ne sont en fait que des raccourcis vers cette méthode execute(), en changeant juste le mode d'hydratation des résultats (objet, tableau, etc.). |
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13 | <?php
// Exécute un UPDATE par exemple :
$qb->getQuery()->execute();
// Voici deux méthodes strictement équivalentes :
$resultats = $query->getArrayResult();
// Et :
$resultats = $query->execute(array(), Query::HYDRATE_ARRAY);
// Le premier argument de execute() est un tableau de paramètres.
// Vous pouvez aussi passer par la méthode setParameter(), au choix.
// Le deuxième argument de execute() est ladite méthode d'hydratation.
|
|
Pensez donc à bien choisir votre façon de récupérer les résultats à chacune de vos requêtes.
Utilisation du Doctrine Query Language (DQL)
Le DQL est une sorte de SQL adapté à l'ORM Doctrine2. Il permet de faire des requêtes un peu à l'ancienne, en écrivant une requête en chaîne de caractères (en opposition au QueryBuilder).
Pour écrire une requête en DQL, il faut donc oublier le QueryBuilder, on utilisera seulement l'objet Query. Et la méthode pour récupérer les résultats sera la même. Le DQL n'a rien de compliqué, et il est
très bien documenté.
La théorie
Pour créer une requête en utilisant du DQL, il faut utiliser la méthode
createQuery() de l'
EntityManager :
Code : PHP | <?php
// Depuis un repository
public function myFindAllDQL()
{
$query = $this->_em->createQuery('SELECT a FROM SdzBlogBundle:Article a');
$resultats = $query->getResult();
return $resultats;
}
|
Regardons de plus près la requête DQL en elle-même :
Code : SQL | SELECT a FROM SdzBlogBundle:Article a
|
Tout d'abord, vous voyez que l'on utilise pas de table. On a dit qu'on pensait objet et non plus base de données ! Il faut donc utiliser dans les FROM et les JOIN le nom des entités. Soit en utilisant le nom raccourci comme on l'a fait, soit le namespace complet de l'entité. De plus, il faut toujours donner un alias à l'entité, ici on a mis "a". On met souvent la première lettre de l'entité, même si ce n'est absolument pas obligatoire.
Ensuite, vous imaginez bien qu'il ne faut pas sélectionner un à un les attributs de nos entités, cela n'aurait pas de sens. Une entité Article avec le titre renseigné mais pas la date ? Ce n'est pas logique. C'est pourquoi on sélectionne simplement l'alias, ici "a", ce qui sélectionne en fait tous les attributs d'un Article. L'équivalent d'une étoile (*) en SQL donc.
Sachez qu'il est tout de même possible de ne sélectionner qu'une partie d'un objet, en faisant a.titre par exemple. Mais vous ne recevez alors qu'un tableau contenant les attributs sélectionnés, et non un objet. Vous ne pouvez donc pas modifier/supprimer/etc l'objet, puisque c'est un tableau. Cela sert dans des requêtes particulières, mais la plupart du temps on sélectionnera bien tout l'objet.
Faire des requêtes en DQL n'a donc rien de compliqué. Lorsque vous les faites, gardez bien sous la main
la page de la documentation sur le DQL pour en connaitre la syntaxe. En attendant, je peux vous montrer quelques exemples afin que vous ayez une idée globale du DQL.
Exemples
Pour faire une jointure :
Code : SQL | SELECT a, u FROM Article a JOIN a.utilisateur u WHERE u.age = 25
|
Pour utiliser une fonction SQL :
Code : SQL | SELECT a FROM Article a WHERE TRIM(a.auteur) = 'winzou'
|
Pour sélectionner seulement un attribut (attention les résultats seront donc sous forme de tableaux et non d'objets) :
Code : SQL | SELECT a.titre FROM Article a WHERE a.id IN(1, 3, 5)
|
Et bien sûr vous pouvez également utiliser des paramètres :
Code : PHP | <?php
public function myFindDQL($id)
{
$query = $this->_em->createQuery('SELECT a FROM Article a WHERE a.id = :id');
$query->setParameter('id', $id);
return $query->getSingleResult(); // Utilisation de getSingleResult car la requête ne doit retourner qu'un seul résultat
}
|