• Aucun résultat trouvé

Les méthodes de récupération personnelles 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 trois 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, cela 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 œil à 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 <?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 tableau 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 raccourci :

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()

->getManager()

->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éthodes 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

<?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();

}

:nom_du_parametre, puis on attribue 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

<?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 cela que ce dernier 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

<?php

// Depuis un repository

{

$qb = $this->createQueryBuilder('a'); // On peut ajouter ce qu'on veut avant

$qb->where('a.auteur = :auteur')

->setParameter('auteur', 'winzou'); // On applique notre condition

$qb = $this->whereCurrentYear($qb); // On peut ajouter 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çons d'extraire les résultats de la requête. Ces différentes manières sont toutes à maîtriser, car elles concernent chacune un type de requête.

getResult()

Exécute la requête et retourne un tableau contenant les résultats sous forme d'objets. 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 tableaux. 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().

<?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 valeurs. 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 valeurs (getArrayResult) ou un objet de valeurs (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

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 retournent 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 exceptions 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 retournent 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

<?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 n'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 connaître la syntaxe. En attendant, je peux vous montrer quelques exemples afin que vous ayez une idée globale du DQL.

Pour tester rapidement vos requêtes DQL sans avoir à les implémenter dans une méthode de votre repository, Doctrine2 nous simplifie la vie grâce à la commande doctrine:query:dql . Cela vous permet de faire quelques tests afin de construire ou de vérifier vos requêtes, à utiliser sans modération donc ! Je vous invite dès maintenant à exécuter la commande suivante :

php app/console doctrine:query:dql "SELECT a FROM SdzBlogBundle:Article a" .

Exemples