Débugger une requête dynamique
Avec Drupal 7 a été introduit une toute nouvelle couche d'accès aux bases de données. Et parmi les nombreuses possibilités offertes par cette Database API, nous trouvons la manipulation dynamiquement des requêtes SQL pour ajouter une jointure, un tri, une condition, etc.
Mais cette fantastique possibilité induit aussi de véritables maux de crâne lorsqu'arrive l'inévitable heure du debuggage.
Requêtes dynamiques avec Drupal 6
Avec Drupal 6, lorsque l'on devait créer une requête à géométrie variable, cela donnait un code de ce genre :
if (!is_null($uid)) {
$bout = "n.uid=$uid"; // oui je sais, ce n'est pas safe, c'est pour l'exemple ;-)
} else {
$bout = '';
}
$query = "
SELECT n.nid, n.title
WHERE
$bout
n.type='mon_type' AND
n.status=1
ORDER BY n.created DESC";
$result = db_query($query);
while ($row = db_fetch_object($result)) {
...
}
Alors pas de doute, c'est moche. Cela peut être rendu moins moche avec les placeholders, mais bon, pas terrible tout de même...
Drupal 7 et db_select
Maintenant, écrivons le même code en utilisant les requêtes dynamiques de Drupal 7 :
// Création d'une requête en définissant le "from"
$query = db_select('node', 'n');
// Ici une clause "where" conditionnée par la présence d'un user ID valide
if (!is_null($uid)) {
$query->condition('n.uid', $uid);
}
// Clause Where : n.type='mon_type'
$query->condition('n.type', 'mon_type');
// Clause Where : n.status=1
$query->condition('n.status', 1);
// On trie le tout
$query->orderBy('n.created', 'DESC');
// Et on itère sur le résultat, tout simplement...
foreach ($query->execute() as $row) {
...
}
Comme vous pouvez le constater, cela n'a pas la même gueule... Le sujet de ce papier n'étant pas d'expliquer en détail le fonctionnement de cette API, je vous conseille d'aller bouquiner l'excellent documentation disponible ici.
Et quant ça coince ?
Seul souci dans cette histoire, comment debugger une requête qui plante. Et justement, la requête donnée plus haut plante salement (c'est même fait exprès ;-). Avant, avec Drupal 6, un simple echo $query aurait suffit. Et avec Drupal 7 ce n'est guère plus compliqué.
En effet, $query est maintenant un objet dont la classe a le bon goût d'implémenter la méthode magique __toString() et de fournir en résultat une requête SQL correctement formée, ou presque. Presque car nous avons encore ces "placeholder" qui nous bouchent la vue. Qu'à cela ne tienne, il suffit d'aller piocher les valeurs de ces placeholders, qui sont les paramètres de notre requêtes, grâce à la méthode $query->getArguments() qui nous renvoie un tableau utilisable par la fonction PHP strtr :
Voilà qui est bien mieux. On voit immédiatement qu'il manque quelque chose d'assez fondamental entre le SELECT et le FROM, les champs à remonter. Cela se corrige facilement :
// Création d'une requête en définissant le "from"
$query = db_select('node', 'n');
// Ajout des champs n.nid et n.title
$query->fields('n', array('nid', 'title'));
Voilà, c'est réglé. Et nous pouvons donc ajouter à nos fonctions pratiques celle qui permet de debugger une requête dynamique :
function db_debug($query) {
return strtr((string)$query, $query->getArguments()+array('{'=>'', '}'=>''));
}
Cette fonction n'est rien de plus qu'une formalisation de la technique énoncée plus haut, à ceci près que je vire au passage les accolades permettant de tester la requête facilement dans une console mysql ou postgresql.
Happy hunting :)
Ou avec Devel, il y a la fonction dpq()
http://drupalcode.org/project/devel.git/blob/refs/heads/7.x-1.x:/devel.m...
(Tu change souvent de design ces temps-ci !)
Là je ne peux pas dire, je n'ai jamais utilisé devel. Faudra que je le teste un jour.
Pour le design, il a pas changé depuis un certain temps. Tu ne confonds pas avec celui d'artisan ?
Pas mieux que Seb avec la fonction dpq() : http://api.drupal.org/api/devel/devel.module/function/dpq/7
Devel apporte quand même plusieurs choses utiles pour développer/déboguer tu devrais vraiment y jeter un coup d’œil :)
Mais j'y ai jeté un coup d'oeil entre temps et j'en ai déjà retiré deux trois choses utiles pour mes propres outils de debuggage (notamment le fonctionnement du logger de requêtes de D7, bien pratique).
Ceci étant dit, je ne nie pas l'intérêt des modules de service. Mais l'objectif de ce site, s'il est est un, c'est d'apprendre à faire, et non d'apprendre à utiliser. Dit autrement savoir que dpq permet de faire ce que cet article décrit c'est bien, mais si l'on se borne à cela, on est dés-facto cantonné à un cadre d'usage restrictif. En revanche savoir comment dpq fonctionne en interne permet d'aller plus loin, soit pour coder un usage que dqp n'autorise pas (par exemple moi je ne logue jamais sur le navigateur, mais dans une console), soit en permettant d'exploiter cette technique dans un autre cadre que celui de dpq (par exemple un outil de monitoring).
Et puis de tout façon, il y a sur le web suffisamment, je pense, de sites dédiés à l'explication de l'usage du module X ou Y. Je ne verrais pas l'intérêt d'en faire un de plus ;-)
En passant, dpq() a le désagréable défaut de passer par dpm(); qui passe donc par les sessions Drupal et leur affichage dans le template page, c'est donc le mal pour débogguer. Par exempmle si on met un exit avant l'affichage du template; ça n'affiche plus rien :D
Y'a rien qui empeche de se faire sa propre version de dpq() avec un kpr() à la place d'un dmp() mais la fonction de yoran fait parfaitement le job en une seule ligne de code.
rectification :
dpq est plus complet : je peux voir les "group by" avec lui et pas avec la fonction light que tu mentionnes dans ton article.
Alors moi je les vois les "group by" ceci dit. Maintenant dans certains cas, tu peux avoir besoin de faire un $query->preExecute() avant l'appel à __toString().
Ceci étant dit, si tu utilises devel, je ne vois pas l'intérêt de mon astuce. Autant utilisé l'existant.
Publier un nouveau commentaire