Nous allons ici explorer un peu plus en profondeur Doctrine et découvrir son langage interne :
le DQL.
DQL signifie
Doctrine Query Language. L'intérêt est encore une fois de ne pas se soucier du type de base de données utilisé. En effet, même s'ils utilisent le langage SQL, beaucoup de SGBD (tous ?

) implémentent leurs propres fonctions et spécificités dans le langage. Doctrine se chargera de traduire le DQL dans le langage approprié.
Sélection simple de données
Rassurez-vous, vous n'allez pas devoir apprendre (encore !) un nouveau langage ultra complexe...
Le DQL utilise en fait les mêmes instructions que le SQL.
Pour ce premier chapitre, nous allons donc utiliser le mot clé...
SELECT !
Tout d'abord, petite explication.
Lorsque vous sélectionnez un objet de cette manière :
Code : PHP | <?php
$article = Doctrine_Core::getTable('Article')->find(1);
|
Doctrine va générer des instructions en SQL. Mais nous avons la possibilité d'écrire nous-mêmes les requêtes à la main.
Pour cela, nous allons utiliser des objets
Doctrine_Query.
Code : PHP | <?php
$q = Doctrine_Query::create()
->from('Article a')
->leftJoin('a.User u')
->where('a.id = ?', 1);
$article = $q->fetchOne();
|
Cet exemple produit le même effet que précédemment, le SQL généré étant le même ; mais à la différence près que nous avons construit la requête à la main.
Rassurez-vous, nous allons détailler tout cela juste après.
Euh... C'est quoi l'intérêt de faire ça alors qu'on peut le récupérer en une seule ligne, notre article ?!
Oui, bon, j'avoue, sur cet exemple, l'intérêt n'est pas flagrant.

Mais dès que vous allez vouloir faire des sélections plus complexes qu'avec les
finders (souvenez-vous, les méthodes
$table->findByXXX()), cela va devenir indispensable.
Imaginons que nos articles possèdent un champ
publishable dont le contenu indique si l'article est publiable ou non. Pour récupérer un article, il nous faut alors prendre ceci en compte et filtrer les résultats :
Code : PHP | <?php
$q = Doctrine_Query::create()
->from('Article a')
->leftJoin('a.User u')
->where('a.id = ?', 1)
->andWhere('a.publishable = ?', true);
$article = $q->fetchOne();
|
SELECT : Doctrine_Query en détails
Passons maintenant en revue les différentes méthodes disponibles pour construire notre
Doctrine_Query.
SELECT
Doctrine_Query::select() permet d'indiquer les champs à sélectionner. Notez que pour l'instant, nous récupérons tous nos enregistrements sous forme d'objets ; il est donc inutile de préciser quels champs récupérer, car Doctrine sélectionne automatiquement tous les champs. Nous verrons plus tard comment personnaliser tout ça.
FROM
Doctrine_Query::from() permet d'indiquer la (ou les) table(s) sur lesquelles effectuer la sélection. Pour ajouter d'autres champs à une requête existante, utilisez
Doctrine_Query::addFrom().
WHERE
Doctrine_Query::where() permet de filtrer les enregistrements. Utilisez la syntaxe « classique » de la clause WHERE du SQL.
Notez que cette méthode efface toutes les conditions précédemment définies. Pour ajouter une condition à celles existantes, il faut utiliser
Doctrine_Query::addWhere() (identique à
Doctrine_Query::andWhere()) qui ajoute une condition avec
AND, ou
Doctrine_Query::orWhere() qui ajoute une condition avec
OR.
Cette fonction accepte en deuxième argument la liste des paramètres :
Code : PHP | <?php
$q->andWhere('a.id = ? OR a.id = ?', array(1, 2));
$q->orWhere('a.title = ?', array('Mon titre'));
// Ou :
$q->orWhere('a.title = ?', 'Mon titre');
// Ou encore :
$q->orWhere('a.title = :title', array(':title' => 'Mon titre'));
|
JOIN
Doctrine supporte deux types de jointures :
INNER JOIN et
LEFT JOIN.
RIGHT JOIN n'est pas supporté, mais il suffit de construire ses requêtes autrement pour y pallier. Les méthodes utilisées sont
Doctrine_Query::innerJoin() et
Doctrine_Query::leftJoin().
Par défaut, Doctrine ajoute automatiquement la condition de jointure (mot-clé
ON) en fonction des relations définies entre les tables. Pour utiliser une condition personnalisée, il suffit d'inclure le mot clé
ON dans le DQL passé à l'une de ces méthodes :
Code : PHP | <?php
$q = Doctrine_Query::create()
->from('Article a')
->leftJoin('a.User u ON u.id = ?', 3);
|
- Le mot-clé WITH (ajout d'une condition sur la jointure) s'utilise de la même manière.
- Les paramètres sont passés de la même façon que pour le WHERE (voir ci-dessus).
ORDER BY
La clause
ORDER BY se définit avec
Doctrine_Query::orderBy() (ou
Doctrine_Query::addOrderBy()).
LIMIT et OFFSET
Utilisez
Doctrine_Query::limit() et
Doctrine_Query::offset().
Pour ceux qui formulent leurs limites en faisant LIMIT 14, 50 (avec MySQL par exemple), sachez que cela ne fait pas partie des spécifications officielles de SQL. L'autre formulation possible est LIMIT 50 OFFSET 14. Avec Doctrine, vous devrez donc utiliser la deuxième manière et utiliser limit() et offset().
GROUP BY
Utilisez
Doctrine_Query::groupBy() ou
Doctrine_Query::addGroupBy().
HAVING
Utilisez
Doctrine_Query::having() ou
Doctrine_Query::addHaving().
Récupération des résultats
Pour finir, la requête doit être exécutée pour pouvoir récupérer les résultats.
Doctrine possède plusieurs méthodes pour cela :
- execute() : cette méthode retourne un objet Doctrine_Collection contenant un enregistrement ou plus. Notez que vous pouvez passer en argument un tableau contenant les paramètres n'ayant pas encore été renseignés. On peut également passer en deuxième argument le mode d'hydratation des résultats, mais nous verrons cela plus tard.
- fetchOne() : cette méthode retourne un objet Doctrine_Record. Cette méthode accepte les mêmes arguments que execute().
Il existe d'autres méthodes qui retournent des résultats, telle que
fetchArray(). Mais nous verrons cela plus tard (oui, encore

).
Note sur le passage des paramètres
Vous avez vu que les méthodes acceptant des
variables peuvent recevoir en paramètre ces valeurs à remplir. Lorsque vous utilisez des valeurs dynamiques comme cela, vous devez
toujours les passer en paramètre. De cette manière, les caractères dangereux seront échappés automatiquement, évitant des injections SQL.
Ces paramètres s'utilisent de la même manière qu'avec PDO. Deux choix s'offrent à vous pour indiquer leur emplacement :
- Utiliser des ?.
Dans ce cas, les paramètres doivent être passés dans le même ordre que les ?.
Par exemple : <?php $q->where('a.title = ?', $title).
- Utiliser des paramètres nommés, comme :name.
Dans ce cas, l'argument supplémentaire doit être un tableau associatif de paires nom => valeur.
Par exemple : <?php $q->where('a.title = :title', array(':title' => $title)).
Pour finir, comme je vous l'ai dit, les méthodes qui exécutent la requête (telles que
execute() et
fetchOne()) acceptent elles aussi comme premier argument un tableau de paramètres à passer.
Pourquoi ? Et bien, il se peut qu'au moment de construire votre requête, vous ne connaissiez pas encore la valeur des variables que vous utiliserez à l'intérieur. Vous avez donc la possibilité de les passer tout à la fin, juste au moment de l'exécution.
Par exemple, les deux codes ci-dessous sont identiques :
Code : PHP - Passage des paramètres lors de la construction | <?php
$q = Doctrine_Query::create()
->from('Article a')
->where('a.title = :title AND a.publishable = :publishable', array(
':title' => $title,
':publishable' => $publishable
))
->andWhere('a.content LIKE :keyword', '%'.$keyword.'%');
$resultats = $q->execute();
|
Code : PHP - Passage des paramètres lors de l'exécution | <?php
$q = Doctrine_Query::create()
->from('Article a')
->where('a.title = :title OR a.publishable = :publishable')
->andWhere('a.content LIKE :keyword');
$resultats = $q->execute(array(
':title' => $title,
':publishable' => $publishable,
':keyword' => '%'.$keyword.'%',
));
|
Ceci peut vous être utile lorsque vous utilisez des requêtes comme
modèles. Notez que dans ce cas, il est conseillé d'utiliser les paramètres nommés (tels que
:title), à moins que vous ne connaissiez par coeur l'ordre dans lequel vous devez passer les paramètres.
UPDATE et DELETE
Les mises à jour et suppressions de données se font tout aussi simplement avec un objet
Doctrine_Query.
DELETE
Ici, au lieu d'utiliser
Doctrine_Query::select(), nous allons utiliser...
Doctrine_Query::delete() !
Surprenant, hein ? 
Nous pouvons appliquer les mêmes filtres que pour un SELECT ; voici un exemple :
Code : PHP | <?php
$q = Doctrine_Query::create()
->delete('Article a')
->where('a.title LIKE ?', 'Mauvais titre')
->orWhere('a.published = ?', false)
->limit(20);
$number = $q->execute();
|
UPDATE
De la même manière, nous pouvons mettre à jour des champs. Il faut en revanche spécifier en plus les champs à mettre à jour avec
Doctrine_Query::set() :
Code : PHP 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | <?php
$q = Doctrine_Query::create()
->update('Article a')
->where('a.title = ?', 'mon titre')
->orWhere('a.id = ?', 1)
// Soit :
->set('content', 'un super texte');
// ou soit :
->set('content', '?', $content);
// ou encore
->set(array('content' => $content))
$number = $q->execute();
|
Le nombre de lignes affectées (mises à jour ou supprimées) est retourné par la méthode execute() dans le cas d'un update d'un delete.