Les fetch modes de PDO

PDO propose une quantité assez impressionante de “modes de récupération” (fetch mode) des données. Entendez par là qu’il est possible de personnaliser le comportement des méthodes fetch et fetchAll pour obtenir les résultats des requêtes SQL sous des formes très diverses. Si certains modes sont relativement classiques et ont des équivalents avec les précédents drivers MySQL (mysql_fetch_array, mysql_fetch_row, etc.), d’autres sont totalement inédits… mais très mal documentés ! Et pourtant, certains sont très pratiques… J’ai donc décidé d’écrire une série d’articles présentant tous les modes de fetch, leur fonctionnement et surtout leurs performances.

Cette première partie est consacrée à la présentation des tests et aux modes “classiques”.

Présentation

Le mode de récupération peut être définis de plusieurs façons :

  • directement en paramètre de la méthode query() ;
  • avec la méthode setFetchMode() appliquée à l’objet de résultats ;
  • en paramètre de la méthode fetchAll() ;
  • en paramètre de la méthode fetch() ; dans ce cas le mode s’applique ligne par ligne, et il est ainsi possible de changer de mode pendant la récupération…

Attention, tous les modes ne peuvent pas s’utiliser avec toutes les méthodes (sinon ça serait trop simple) : certains fonctionnement seulement avec fetchAll, d’autres avec fetch, d’autres encore nécessitent obligatoirement un appel à setFetchMode pour passer des paramètres supplémentaires… C’est l’anarchie et ce n’est pas du tout documenté sur le site de PHP, alors si besoin est, je précise comment utiliser le mode.

Rappel à propros de fetchAll : il n’est pas recommandé de l’utiliser pour traiter de grosses requetes, car cela revient à mettre d’abord en mémoire la *totalité* des résultats pour les traiter ensuite. En utilisant fetch dans une boucle while, on met en mémoire une ligne et on la traite, puis la suivante réutilise la mémoire occupée, etc.

Les tests sont effectués avec PHP 5.2.4 (certains modes sont très récents). Le code utilisé pour afficher le résultat est le suivant (sauf mention contraire) :

$dbh = new PDO($dsn, $login, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql = 'SELECT id, login FROM user LIMIT 0,2';
$results = $dbh->query($sql);
print_r($results->fetchAll(PDO::FETCH_<em>FOOBAR</em>));

Enfin, je suppose que vous connaissez déjà PDO avant de lire cet article :-)

FETCH_BOTH

C’est le mode de récupération par défaut de PDO, et, selon moi, le moins utile et le moins efficace. Le tableau qui est retourné contient les résultats en double : une fois indexés par le nom des colonnes et une deuxième fois indexés par leur numéro. Personnellement, je n’utilise *jamais* les deux modes d’indexation en même temps ; c’est soit l’un, soit l’autre. Du coup, ce mode de récupération ne sert à rien. Pourquoi est-ce que c’est justement celui là le mode par défaut ? Mystère.

Exemple :

$results->fetchAll(PDO::FETCH_BOTH);
Array
(
  [0] => Array
    (
      [id] => 1
      [0] => 1
      [login] => toto
      [1] => toto
    )

  [1] => Array
    (
      [id] => 2
      [0] => 2
      [login] => titi
      [1] => titi
    )
)

J’ai mesuré le temps moyen mis à récupérer les résultats d’une requête de 100 lignes (sur 1000 executions), et la taille du tableau retourné, avec fetchAll, puis avec while ( $r = $results->fetch() ). Puisque c’est le mode par défaut, les résultats me servent de référence pour comparer avec les autres modes.

Note : depuis PHP 5.2.0 il est possible de modifier le mode de récupération par défaut avec :

$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_<em>FOOBAR</em>);

Ce mode sera utilisé lorsqu’aucun mode n’est précisé, mais parfois PDO revient malgré tout à FETCH_BOTH, alors on ne peut pas dire que ça soit très fiable.

FETCH_NUM

Le tableau retourné contient les résultats indexés par le numéro de la colonne.

Exemple :

$results->fetchAll(PDO::FETCH_NUM);
Array
(
  [0] => Array
    (
      [0] => 1
      [1] => toto
    )

  [1] => Array
    (
      [0] => 2
      [1] => titi
    )

)

Par rapport à FETCH_BOTH :

  • Temps de récupération : -7,1%
  • Taille du résultat : -23%

Ce mode est le plus rapide et retourne le tableau le plus petit. Mais il n’est pas toujours pratique de travailler avec le numéro des colonnes. D’autant plus que le moindre changement dans la requete SQL (suppression d’une colonne par exemple) risque de casser tout le reste du script ! En clair, à utiliser dans des cas très spécifiques et quand le nombre de colonnes est réduit pour ne pas risquer d’avoir une application impossible à maintenir.

FETCH_ASSOC

Le tableau retourné contient les résultats indexés par le nom de la colonne. Ce mode ne permet pas de récupérer plusieurs colonnes avec le même nom (une seule valeur est retournée, en général celle la plus à droite dans la requête, i.e. la dernière).

Exemple :

$results->fetchAll(PDO::FETCH_ASSOC);
Array
(
  [0] => Array
    (
      [id] => 1
      [login] => toto
    )

  [1] => Array
    (
      [id] => 2
      [login] => titi
    )

)

Par rapport à FETCH_BOTH :

  • Temps de récupération : -4%
  • Taille du résultat : -20%

Les performances sont legèrement moins bonnes que FETCH_NUM (mais toujours bien meilleures que le mode par défaut), mais un tableau associatif est *tellement* plus pratique à utiliser et plus facile à maintenir que ça n’a pas d’importance. C’est le mode que j’utilise par défaut.

FETCH_NAMED

Ce mode un peu particulier est identique à FETCH_ASSOC, sauf dans le cas où plusieurs colonnes portent le même nom dans la requete SQL : FETCH_ASSOC ne va retourner qu’une seule valeur tandis que FETCH_NAMED va retourner un tableau de valeurs.

Prenons par exemple la requete suivante (complètement inutile, juste pour l’exemple) :

SELECT email as id, id, login FROM user LIMIT 0,2;

Le résultat avec FETCH_ASSOC est identique à ci-dessus, et la première colonne n’est pas accessible. Avec FETCH_NAMED, on obtient :

$results->fetchAll(PDO::FETCH_NAMED);
Array
(
  [0] => Array
    (
      [id] => Array
        (
          [0] => 1
          [1] => toto@foo
        )

      [login] => toto
    )

  [1] => Array
    (
      [id] => Array
        (
          [0] => 2
          [1] => titi@foo
        )

      [login] => titi
    )

)

Bon, entre nous, si vous avez un jour besoin de ce mode, envisagez d’utiliser les alias de colonnes en SQL…

A suivre : les modes orientés objets, les modes spéciaux et les modes modificateurs.

5 thoughts on “Les fetch modes de PDO

  1. Pingback: Tilàcica » Blog Archive » 10 bonnes raisons d’utiliser PDO (PHP Data Object)

  2. Foo_Toto

    Dommage de ne pas avoir soulevé FETCH_OBJ

    Les résultats n’ont pas la même forme non plus selon que l’on utilise fetch ou fetchAll

    fetch autorisera avec un FETCH_ASSOC un affichage du type:

    $result[‘nomChamps’]

    Alors qu’un qu’un fetchAll oblige à indicer le tableau si on veut afficher la 1ere valeur, on a alors:

    $result[0][‘nomChamps’]

    Ou alors il faut l’extraire dans une boucle du type

    foreach($result as $row){

    echo $row[‘nomChamps’]
    }

    La différence entre fetch et fetchAll est vraiment très peu documentée, malgré toute la littérature disponible sur PDO en 2010.

    Good luck

  3. Buzut

    Merci beaucoup pour cette mise au point qui clarifie nettement ce que raconte la doc :)

    J’avais jamais songé à la différence de performances de fetch d’avec fetchAll, même si, quand on y pense, c’est une évidence.

    J’apprécie notamment fetchAll parce qu’il est possible de faire un closeCursor immédiatement apres et de traiter la variable plus loin dans le code. Tandis qu’avec un fetch, il faut avant tout faire un while pour récupérer toutes les données si on veut libérer la connexion pour une autre requête.

  4. Pingback: Lumière sur les fetch modes de PDO - Buzut

  5. Pingback: Le labo PHP de Greg

Comments are closed.