Afficher la liste des enregistrements d'une table MySQL sur plusieurs pages

vendredi 5 mai 2006

Il est courant de présenter les enregistrements d'une base de données sous forme de liste. C'est même très simple a réaliser si l'on veut afficher grâce au PHP tous les enregistrements d'une table MySQL. Cependant, un problème surgit tôt ou tard: il y a trop d'enregistrements dans la table, et ils ne peuvent pas tous être affichés sur la même page. La raison est évidente: il n'est pas raisonnable de présenter une liste de plusieurs dizaines de pages de long alors que l'utilisateur n'a besoin que des premiers enregistrements de la liste. Charge des milliers d'enregistrements dans un tableau aux proportions démesurées va à l'encontre de toute optimisation du code: la page sera longue à charger, lourde à manipuler, et pourra même aller jusqu'à faire planter le navigateur s'il y a plusieurs mégaoctets de données à afficher.

La solution ? Afficher la liste sur plusieurs pages de X enregistrements chacune, avec en en-tête de la liste deux petits boutons "Précédente" et "Suivante" qui permettent de passer d'une page à l'autre. Eh bien, c'est plus facile à dire qu'à faire. On se heurte en effet très tôt à une difficulté: il faut prévoir les limites de la liste et enlever le bouton "Précédente" sur la première page et le bouton "Suivante" sur la dernière, sous peine de générer une erreur lors du passage de la requête SQL.

Il est courant de présenter les enregistrements d’une base de données sous forme de liste. C’est même très simple a réaliser si l’on veut afficher grâce au PHP tous les enregistrements d’une table MySQL. Cependant, un problème surgit tôt ou tard: il y a trop d’enregistrements dans la table, et ils ne peuvent pas tous être affichés sur la même page. La raison est évidente: il n’est pas raisonnable de présenter une liste de plusieurs dizaines de pages de long alors que l’utilisateur n’a besoin que des premiers enregistrements de la liste. Charge des milliers d’enregistrements dans un tableau aux proportions démesurées va à l’encontre de toute optimisation du code: la page sera longue à charger, lourde à manipuler, et pourra même aller jusqu’à faire planter le navigateur s’il y a plusieurs mégaoctets de données à afficher.

La solution ? Afficher la liste sur plusieurs pages de X enregistrements chacune, avec en en-tête de la liste deux petits boutons «Précédente» et «Suivante» qui permettent de passer d’une page à l’autre. Eh bien, c’est plus facile à dire qu’à faire. On se heurte en effet très tôt à une difficulté: il faut prévoir les limites de la liste et enlever le bouton «Précédente» sur la première page et le bouton «Suivante» sur la dernière, sous peine de générer une erreur lors du passage de la requête SQL.

Quelques explications sont sans doute les bienvenues. MySQL nous offre un moyen simple pour choisir quels enregistrements afficher: la clause LIMIT. Elle fonctionne ainsi: LIMIT x, zx est le point de départ et z la distance à parcourir. Exemple: SELECT * FROM matable LIMIT 0, 10 récupère 10 enregistrements à partir de 0, soit les 10 premiers enregistrements ; SELECT * FROM matable LIMIT 5, 10 récupère 10 enregistrements à partir de 5, soit les enregistrements 6 à 15. J’ai bien écrit 6, et pas 5. Le premier enregistrement est en effet indexée à 0, et les bornes sont incluses dans la sélection. LIMIT 0, 10 renvoie donc l’intervalle [0,9] et LIMIT 5, 10 l’intervalle [6, 15]. On comprend que x, qui correspond à la première borne de l’intervalle, peut également être considéré comme le premier enregistrement de la page suivante.

Pour connaître le nombre d’enregistrements dans la table, il faudra effectuer une requête préalable. En PHP, il faudra ainsi écrire:

  • $sql = "SELECT * FROM matable;";
  • $req = mysql_query($sql);
  • $t = mysql_num_rows($req);

</span>.
La commande mysql_num_rows permet, comme vous l’aurez compris, de récupérer le nombre (num) de lignes (rows) de la table considérée. Outre les variables x et z déjà définies, nous obtenons t qui correspond au nombre total d’enregistrements, c’est-à-dire à l’index du dernier enregistrement, c’est-à-dire à la borne supérieure de notre intervalle global.

Avec cela, nous pouvons avancer, mais pas reculer. Plus précisément, nous pouvons calculer l’intervalle des enregistrements correspondant à la page qui suit la page courante, mais pas l’intervalle correspondant à celle qui la précède. Pour afficher la page précédente, il nous faut, comme il a été expliqué ci-dessus, les deux paramètres de la clause LIMIT: le point de départ et la distance à parcourir. La distance à parcourir ne change pas, il s’agit toujours de notre variable z. Le point de départ, nous l’appellerons y.

Nous voilà donc en présence des variables suivantes:

  • x: le premier enregistrement de la page suivante
  • y: le premier enregistrement de la page précédente
  • z: le nombre d'enregistrements à afficher
  • t: le nombre total d'enregistrements

schéma

Commençons à élaborer le code. La première chose à faire est d’afficher la première page: l’intervalle [x=0,z]. La variable x est forcément égale à 0, puisque l’index du premier enregistrement est 0 (offset). L’intervalle, matérialisé par la variable z peut être n’importe quoi (il ne peut pas être négatif, bien entendu, et ne peut pas être supérieur à t). Enfin, y sera initialisé à la même valeur que z, puisque s’il s’agit du premier intervalle, il n’y a par définition aucun intervalle avant lui.

  • $x = 0;
  • $z = 10;
  • $y = $z;

</span>
Il ne nous reste plus qu’à former la requête SQL:
SELECT * FROM matable LIMIT $x,$z;

Il faut maintenant préparer les boutons «page suivante» et «page précédente». L’idée est la suivante: cliquer sur ces boutons appelle une URL (ou une méthode directement, avec AJAX…) qui contient les valeurs de x et de z respectivement pour la page qui suit et pour celle qui précède la page courante.

Pour la page précédente d’abord. Il faut calculer la valeur de y, le premier enregistrement de la page précédente. y est égal à x - z: $y = $x - $z;. Mais un problème se pose: que se passe-t-il si z est supérieur à x ? On sait que y ne peut en aucun cas être un nombre négatif, puisque l’index du premier enregistrement est 0. Si la requête est formée avec un y négatif, une erreur sera générée. Un simple test permet de parer à cette éventualité: if($y < 0) { $y = 0; }.
</span>

Pour la page suivante, le raisonnement est le même. Le schéma ci-dessus l’illustre parfaitement, le nouveau x est l’ancien plus z: $x = $x + $z;. Mais à nouveau un problème se pose: si x+z devient supérieur à t. En effet, z est fixé, par exemple à 10. x varie, disons qu’il est pour l’instant à 90. Quant à t, il est égal à 95 puisqu’il y a 95 enregistrements dans la table. Si l’on applique bêtement x+z, on trouvera 90+10=100, ce qui est supérieur à 95. On essaie d’afficher des enregistrements qui n’existent pas ! Il faut donc toujours contrôler que x+z (ou le «nouveau» x) ne dépasse pas t, et si c’est le cas, revenir à sa valeur antérieure (qui était par définition correcte et inférieure à t): if($x >= $t) { $x = $x - $z; }.

Dernier problème à surmonter: on veut masquer le bouton «précédent» lorsqu’on se trouve sur la première page, et le bouton «suivant» lorsqu’on se trouve sur la dernière page. Dans le premier cas, il faudra contrôler que x - z est positif: si c’est le cas, c’est que la page courante n’est pas la première. En effet, la première page contient les enregistrements dans l’intervalle [x=0,x+z]. Dans le second cas, il faudra contrôler que x + z est inférieur à t. En effet, la dernière page représente l’intervalle [x,(x+z)< t]. Le code pourrait ressembler à cela:

  • if($x > $z)
  •    echo 'Page précédente';
  • if($x < $t)
  •    echo 'Page suivante';

</p>

Un petit exemple:

  • //On récupère la dernière valeur de x
  • $x = $_GET['debut'];
  • //On fixe z et on détermine t
  •   $sql = "SELECT * FROM matable;";
  •   $req = mysql_query($sql);
  • $t = mysql_num_rows($req);
  • $z = 10;
  • //On forme la requête SQL
  • $sql = "SELECT * FROM matable ORDER BY id DESC LIMIT $x,$z;";
  • //On prépare les nouvelles valeurs de x et y
  • $y = $x - $z;
  • if($y < 0)
  •   $y = 0;
  • $x = $x + $z;
  • //Doit-on afficher le bouton "précédent" ?
  • if($x > $z)
  •   echo "<a href="monfichier.php?debut=$y">Page précédente</a>";
  • //Doit-on afficher le bouton "suivant" ?
  • if($x < $t)
  •   echo "<a href="monfichier.php?debut=$x">Page précédente</a>";

</span>

Pour finir, quelques petites données supplémentaires:
- Le premier enregistrement de la dernière page: $l = $t - $z
- Le nombre de pages: ceil($t / $z)
(ceil arrondit à l’entier supérieur)
- La page courante est la Nième page, n = ceil($x / $z)
- La première page: LIMIT 0,$z
- La dernière page: LIMIT $l,$z