Licence CC BY-NC-SA

Paginer avec PHP et MySQL

Afin de ne plus submerger d'informations les utilisateurs, et accélérer le chargement de vos pages

Publié :
Auteur :
Catégories :

Jusqu'à présent, toutes les données d'une table s'affichaient sur une seule page. Mais voilà, cela commence à faire beaucoup de données. On doit faire défiler pour trouver l'information, et ça met de plus en plus de temps pour charger une page. Il faut faire quelque chose.
Oui, mais quoi ?

La solution la plus répandue s'appelle la pagination, soit la séparation en plusieurs pages d'une liste de données.

Ce tutoriel, pensé pour ceux qui ne connaissent pas nécessairement tout de MySQL ni de PHP, est là pour vous expliquer plus concrètement le principe, exposer la réflexion pour arriver à une bonne solution, et évidemment vous proposer une implémentation possible.

Malgré le fait que du code soit fourni, ce tutoriel nécessite tout de même quelques bases. Vous devriez être déjà capable d'écrire le code de base et de comprendre ce qu'il fait avant d'aller plus loin.

Cela implique donc de connaître :

  • la syntaxe de PHP
  • les structures de contrôle et les boucles (if, while et for présentes dans ce tutoriel)
  • la connexion aux bases de données (dans l'idéal avec PDO, utilisé ici, et que je vous recommande chaudement)
  • le principe et la syntaxe des requêtes SQL avec MySQL (un tutoriel est disponible sur ce site en cas de besoin)

N'afficher qu'un nombre défini d'éléments

Pour commencer, prenons un code simple qui affiche toutes ces données. Il nous servira aussi de base pour continuer le tutoriel, mais je n'en reprendrai que quelques parties au gré des points à observer.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

// Partie "Requête"
$query = 'SELECT * FROM `my_table`';
$resultSet = $cnx->query($query);

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

Le code de base qui nous servira pour le tutoriel.

Des solutions peu optimales…

Maintenant que nous avons notre code de travail, mettons-nous à l'ouvrage !  :)
Il nous a été demandé de n'afficher que 10 éléments à la fois.

Le compteur d'itérations

En regardant la ligne 9, on voit de suite qu'on va avoir autant d'itérations qu'il y a de résultats.
Du coup, la première solution qui vient à l'esprit, c'est de placer une condition sur le nombre de résultats traités pour sortir de la boucle au bon moment, ce qui nous donnerait ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<?php
// Partie "Boucle"
$limite = 10;
$compteur = 0;
while ($element = $resultSet->fetch()) {
    if ($compteur >= $limite) {
        break;
    }
    // C'est là qu'on affiche les données  :)
    $compteur++;
}

Mise en place d'une condition sur le nombre d'itérations

Cette méthode fonctionne, vous vous frottez les mains d'avoir trouvé une solution rapidement et qui plus est simple à mettre en place.
Malheureusement, elle reste consommatrice de mémoire de manière inutile : nous allons récupérer tous les résultats pour n'en utiliser au final que 10. Si ce n'est pas trop gênant quand il n'y a que 20 enregistrements, imaginez quand il y en a au-delà de 8000…

La requête dans la boucle

Si le souci est qu'on récupère trop de résultats, nous pouvons alors mettre la requête dans la boucle, ce qui nous donnerait quelque chose dans le genre de ce qui suit :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

// Partie "Boucle"
$limite = 10;
for ($i = 0; $i < $limite; $i++) {
    // Partie "Requête"
    $query = 'SELECT * FROM `my_table`';
    $resultSet = $cnx->query($query);
    $element = $resultSet->fetch();
    // C'est là qu'on affiche les données  :)
}

Requête dans la boucle

Mais… mais… ça nous affiche toujours le même élément ⁈

Hé oui, ce qui semblait être une solution logique implique plus de changements que ce qu'on pourrait penser. Il faudrait savoir quel élément a déjà été affiché, et récupérer le premier qui ne l'a pas été. Nous pourrions simplifier, et ne garder que le dernier. Mais il faudrait…

STOP

Cette solution, outre la complexité qu'elle amène sans en avoir l'air, n'est toujours pas optimale. En plus de la mémoire nécessaire pour garder les résultats à portée de main, il faut aussi penser aux temps d'exécution des requêtes.

Un gestionnaire de bases de données est un serveur comme Apache, nginx ou IIS. On lui envoie une requête qu'il traite avant d'envoyer la réponse, que PHP stocke en mémoire. On voit donc qu'il y a au final plus d'un point à prendre en compte :

  1. Le temps d'accès au serveur
    On ne peut pas trop influer dessus, cela dépend de la machine (ou des machines) sur laquelle (ou lesquelles) sont installés PHP et MySQL.
  2. Le temps de traitement
    C'est très lié à la complexité de la requête et à la quantité de résultats, nous pouvons donc en tout cas essayer de demander juste ce qu'il nous faut. Mais ce n'est en général pas le plus gênant, les gestionnaires de bases de données sont suffisamment performants sur ce point.  ^^
  3. La mémoire pour enregistrer cette réponse
    Là aussi, essayer de demander uniquement ce que l'on va utiliser permet déjà de faire mieux.

Au final, c'est donc le temps d'accès qui pose problème. Ce temps d'accès va rester quasiment identique à chaque requête exécutée, donc il faut trouver un moyen pour le diminuer.
Si nous arrivions à mettre en place la seconde proposition, ce temps serait conséquent. La solution est donc de demander directement à MySQL uniquement 10 résultats à la fois. Une seule requête au lieu de 10, donc :

  • nous n'avons pas 10 fois le temps d'accès qui entre en compte
  • nous ne surchargeons pas la mémoire de résultats inutiles
  • nous ne nous cassons plus trop la tête pour savoir lequel est affiché, lequel ne l'est pas  :D

Néanmoins, il nous manque encore la manière de faire pour dire à MySQL de se limiter à 10 résultats.

Une solution optimale avec LIMIT

Pour ceux qui connaissent un peu MySQL ou qui sont allés chercher des solutions ailleurs, vous connaissez peut-être déjà la clause LIMIT. Et pour ceux qui la découvrent maintenant, on va expliquer ce qu'elle permet.  ;)

LIMIT : rien que le mot est évocateur. Et il se trouve que c'est bien l'anglais pour « limite ». Il sert donc à limiter le nombre de résultats retournés par une requête — il n'y a d'ailleurs pas que ça, mais nous ne le verrons pas ici.
Ce qu'il faut retenir, c'est que nous avons désormais de quoi n'avoir que les 10 enregistrements souhaités. Si nous reprenons la partie "Requête" de notre code de travail de départ et que nous l'adaptons, nous avons ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

$limite = 10;

// Partie "Requête"
/* On construit la requête, en remplaçant les valeurs par des marqueurs. Ici, on
 * n'a qu'une valeur, limite. On place donc un marqueur là où la valeur devrait se
 * trouver, sans oublier les deux points « : » */
$query = 'SELECT * FROM `my_table` LIMIT :limite';
/* On prépare la requête à son exécution. Les marqueurs seront identifiés */
$query = $cnx->prepare($query);
/* On lie ici une valeur à la requête, soit remplacer de manière sûre un marqueur par
 * sa valeur, nécessaire pour que la requête fonctionne. */
$query->bindValue(
    'limite',         // Le marqueur est nommé « limite »
     $limite,         // Il doit prendre la valeur de la variable $limite
     PDO::PARAM_INT   // Cette valeur est de type entier
);
/* Maintenant qu'on a lié la valeur à la requête, on peut l'exécuter pour en récupérer
 * le résultat */
$resultSet = $query->execute();

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

La solution optimale, qui va nous servir de base pour la suite, avec quelques explications supplémentaires sur les modifications

Nous avons désormais un code qui nous affiche juste le nombre souhaité d'éléments, qui est rapide, et qui ne consomme pas trop de mémoire. C'est là un bon début.

Ce qu'il nous faut maintenant, c'est de quoi afficher les éléments suivants. Car si nous n'avons plus 10 fois le même, les autres que les 10 premiers doivent être accessibles aussi !  :p

Générer les liens vers les pages précédente et suivante

Maintenant que nous savons comment afficher quelques éléments à la fois, nous allons voir comment faire en sorte d'avoir la possibilité de voir toutes les pages, pas seulement la première.

Comme auparavant, reprenons une base de départ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

$limite = 10;

// Partie "Requête"
$query = 'SELECT * FROM `my_table` LIMIT :limite';
$query = $cnx->prepare($query);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
$resultSet = $query->execute();

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

Base pour la génération des liens vers les autres pages

Et, une fois n'est pas coutume, un peu de réflexion s'impose avant de foncer tête baissée dans le code.

Nous savons que l'on aimerait afficher 10 éléments à la fois. Seulement, il nous faut de quoi afficher les 10 suivants, et aussi les 10 précédents, bref, de pouvoir naviguer d'une page à la suivante ou la précédente.

Liens « Suivant » et « Précédent »

Dans un premier temps, nous allons nous contenter de deux simples liens pour passer d'une page à l'autre. Mais d'abord, réfléchissons.

Une page, ce sont 10 éléments d'affichés. La page suivante, ce sont les 10 éléments suivants, la page précédente, ce sont les 10 éléments précédents. Il nous faut donc adapter notre requête afin de pouvoir dire, d'une certaine manière, que nous n'avons pas besoin des éléments des pages précédentes.

Ce moyen est proposé par un autre mot-clé MySQL : OFFSET. Celui-ci s'utilise très simplement de la manière OFFSET :debut. Comme vous pouvez l'avoir déjà compris, ce mot-clé permet de déterminer à partir de quel enregistrement l'on aimerait récupérer les données. D'une certaine manière, c'est donc aussi le nombre d'enregistrements que l'on souhaite "éviter" ou "ignorer". Je n'ai donc pas choisi les marqueurs sans y réfléchir ! ;)

Maintenant, le souci est de calculer cette valeur de début. Regardons de plus près quels sont les enregistrements que nous souhaitons récupérer pour chaque page. Afin de nous y retrouver, nous allons simplement les numéroter, mais pour des questions pratiques, je vais commencer à 0 comme pour les tableaux en PHP. Ce choix se justifie quand nous apprenons que pour MySQL, le premier enregistrement est l'enregistrement 0.

Sur la première page, nous récupérons donc les enregistrements 0, 1, 2, 3, …, 9 (vous pouvez compter, ça fait bien 10 :p ).
Sur la seconde page, nous allons récupérer les enregistrements 10, 11, 12, …, 19.
Sur la troisième : 20, 21, … 29.
Vous voyez où je veux en venir ?

En fait, sur chaque page, on commence avec l'enregistrement dont la dizaine correspond au numéro de page moins un. Dit autrement, le numéro du premier enregistrement à récupérer pour une page correspond à l'expression ci-dessous :

$$debut = (numeroDePage - 1) * 10$$

Il y a juste encore le 10 qui peut gêner, bien qu'on puisse rapidement se rendre compte que ce 10 correspond au nombre d'éléments par page. Ce serait donc intéressant de le remplacer par $limite$.

$$debut = (numeroDePage - 1) * limite$$

Nous pouvons désormais adapter la requête pour y ajouter ce que nous avons vu plus haut, du fait que nous savons maintenant comment calculer ce dont elle a besoin pour être utilisable comme on le souhaite.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
// Partie "Requête"
/* On calcule le numéro du premier élément à récupérer */
$debut = ($page - 1) * $limite;
/* La requête contient désormais l'indication de l'élément de départ,
 * avec le nouveau marqueur … */
$query = 'SELECT * FROM `my_table` LIMIT :limite OFFSET :debut';
$query = $cnx->prepare($query);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
/* … auquel il faut aussi lier la valeur, comme pour la limite */
$query->bindValue('debut', $debut, PDO::PARAM_INT);
/* Le reste se passe comme auparavant */
$resultSet = $query->execute();

Notre nouvelle partie "Requête", avec les commentaires

Nous sommes allés un peu trop vite, il nous manque quelque chose, là !
Maintenant que nous avons de quoi calculer le numéro du premier enregistrement pour la page, il nous faut encore savoir sur quelle page on se trouve. Pour ce faire, nous allons rester simple : nous allons passer le numéro de la page souhaitée dans l'URL en utilisant un paramètre de chaîne de requête page. Voilà, nous allons enfin pouvoir passer aux liens vers les pages précédentes et suivantes, en ajoutant simplement la partie "Liens".  ^^

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

$page = $_GET['page'];
$limite = 10;

// Partie "Requête"
/* On calcule donc le numéro du premier enregistrement */
$debut = ($page - 1) * $limite;
/* On ajoute le marqueur pour spécifier le premier enregistrement */
$query = 'SELECT * FROM `my_table` LIMIT :limite OFFSET :debut';
$query = $cnx->prepare($query);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
/* On lie aussi la valeur */
$query->bindValue('debut', $debut, PDO::PARAM_INT);
$resultSet = $query->execute();

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

// Partie "Liens"
/* Notez que les liens ainsi mis vont bien faire rester sur le même script en passant
 * le numéro de page en paramètre */
?>
<a href="?page=<?php echo $page - 1; ?>">Page précédente</a><a href="?page=<?php echo $page + 1; ?>">Page suivante</a>

Une pagination effective, mais…

Il reste un point à traiter avant que ce script ne soit utilisable.
Si l'on arrive dessus sans spécifier de numéro de page, on aura droit à un message comme quoi l'index page n'existe pas. Même si nous savons que l'on doit arriver sur la première page, notre script ne le sait pas !  ^^

Il ne manque pas grand-chose pour régler cela, il suffit de modifier un petit peu la récupération du numéro de page comme suit :

3
4
<?php
$page = (!empty($_GET['page']) ? $_GET['page'] : 1);

Définition d'un numéro de page par défaut

Le code ainsi fourni n'est pas totalement sécurisé.

Il vous appartient donc de faire les vérifications d'usage sur ce que contient $_GET['page'].

Voilà, nous avons maintenant quelque chose de fonctionnel, qu'on va pouvoir continuer d'améliorer.

Afficher des liens vers d'autres pages que les suivante et précédente

Maintenant que nous savons comment afficher quelques éléments à la fois, nous allons voir comment faire en sorte d'avoir la possibilité de savoir combien de pages il y a, et permettre de passer à d'autres pages que celles qui suivent ou précèdent immédiatement.

Comme auparavant, reprenons une base de départ.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

$page = (!empty($_GET['page']) ? $_GET['page'] : 1);
$limite = 10;

// Partie "Requête"
$debut = ($page - 1) * $limite;
$query = 'SELECT * FROM `my_table` LIMIT :limite OFFSET :debut';
$query = $cnx->prepare($query);
$query->bindValue('debut', $debut, PDO::PARAM_INT);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
$resultSet = $query->execute();

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

// Partie "Liens"
?><a href="?page=<?php echo $page - 1; ?>">Page précédente</a><a href="?page=<?php echo $page + 1; ?>">Page suivante</a>

Base pour la génération des liens vers les autres pages

Comme toujours, un peu de réflexion s'impose avant de foncer tête baissée dans le code. Nous savons que nous aurons 10 éléments par page, mais nous ne savons pas combien de fois cela va faire. Il nous faut donc un moyen de récupérer ce nombre, qui correspondra au nombre de pages total.

Un peu de réflexion (ou de recherche ^^ ) nous permet de trouver que le nombre de pages total se calcule de la sorte :

$$nombreDePages = \left \lceil{\dfrac{nombredElements_{total}}{limite}}\right \rceil$$

La notation $\lceil{quelqueChose}\rceil$ se lit comme la fonction plafond de $quelqueChose$, et consiste à prendre l'entier supérieur ou égal à l'argument.
Du coup, pour une explication en français : le nombre de pages total est égal à la fonction plafond appliquée sur le résultat de la division du nombre total d'éléments à afficher par le nombre d'éléments par page.
Prenez le temps de relire si vous ne comprenez pas.  :p

Parmi tout ce qu'il y a dans cette expression, il nous manque maintenant $nombredElements_{total}$.

Récupérer le nombre d'éléments total

Nous pourrions commencer par nous dire qu'il nous faut récupérer tous les résultats, puis les compter. Seulement, nous avons déjà vu que récupérer tous les résultats n'est pas une bonne solution quand on n'en affiche que quelques-uns : nous nous retrouvons dans la première situation de la première partie. Il nous faut donc éviter cela.

Pour compter un nombre d'enregistrements, il existe la fonction SQL count(). Elle s'utilise sur une colonne, et compte le nombre de valeurs non-nulles qui s'y trouvent.
Seulement, si vous arrivez à la combiner avec la version courante de notre requête, vous ne récupérerez plus jamais qu'un seul enregistrement. :-°

La vraie solution est d'utiliser un autre mot-clé de MySQL : SQL_CALC_FOUND_ROWS. Ceci, placé juste après SELECT dans une requête, fait en sorte que MySQL conserve une trace du nombre total de résultats trouvés, sans tenir compte de LIMIT : c'est donc exactement ce qu'il nous faut.

Pour l'instant, la requête se présente ainsi :

1
SELECT * FROM `my_table` LIMIT :limite OFFSET :debut

Requête qui ne fait que récupérer limite enregistrements depuis debut

Avec celle-ci, nous récupérons toutes les colonnes des enregistrements. Il nous faut juste ajouter le mot-clé que nous avons vu un peu plus haut, ce qui nous donne la requête suivante :

1
SELECT CALC_FOUND_ROWS * FROM `my_table` LIMIT :limite OFFSET :debut

Requête qui récupère limite enregistrements depuis debut et nous compte le nombre total d'éléments

Il y a une subtilité avec l'utilisation de CALC_FOUND_ROWS : comme je l'ai mis dans la légende du code, le nombre n'est pas récupéré avec les autres données, il faut le récupérer à part. Cela se fait avec la requête suivante :

1
SELECT found_rows()

Récupération du nombre enregistré du fait de CALC_FOUND_ROWS

On voit qu'on demande une valeur du fait qu'il s'agit d'une requête commençant par SELECT. found_rows() est une fonction particulière de MySQL qui va justement récupérer ce qui aura été enregistré lors de la dernière requête SELECT. Je ne vais pas trop m'attarder là-dessus, si vous souhaitez creuser le sujet, je vous conseille la documentation officielle.

Cette dernière requête récupère bien le nombre de résultats totals de la dernière requête SELECT, et pas obligatoirement avec CALC_FOUND_ROWS. Il faut donc éviter de relancer une autre requête entre celle dont vous souhaitez avoir le nombre et la récupération de celui-ci.

Au final, notre partie "Requête" va se présenter ainsi :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php
// Partie "Requête"
$debut = ($page - 1) * $limite;
/* Ne pas oublier d'adapter notre requête */
$query = 'SELECT CALC_FOUND_ROWS * FROM `my_table` LIMIT :limite OFFSET :debut';
$query = $cnx->prepare($query);
$query->bindValue('debut', $debut, PDO::PARAM_INT);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
$resultSet = $query->execute();

/* Ici on récupère le nombre d'éléments total. Comme c'est une requête, il ne
 * faut pas oublier qu'on ne récupère pas directement le nombre.
 * De plus, comme la requête ne contient aucune donnée client pour fonctionner,
 * on peut l'exécuter ainsi directement */
$resultFoundRows = $cnx->query('SELECT found_rows()');
/* On doit extraire le nombre du jeu de résultat */
$nombredElementsTotal = $resultFoundRows->fetchColumn();

La dernière version de la partie "Requête"

Il ne nous reste maintenant plus qu'à afficher les liens vers les autres pages selon leur numéro.

Liste des pages

Nous avons vu une expression mathématique un peu plus haut. Dans un premier temps, nous allons l'adapter en PHP. La fonction qui permet de récupérer l'entier supérieur ou égal le plus proche est la fonction ceil(). Du coup, nous aurons notre calcul qui se présentera ainsi :

1
2
<?php
$nombreDePages = ceil($nombredElementsTotal / $limite);

Adaptation de la formule mathématique dans PHP

Et maintenant que nous avons toutes les données, il nous faut les afficher. Rien de plus simple qu'une boucle pour ce faire.  ;)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
// Partie "Liens"
/* On calcule le nombre de pages */
$nombreDePages = ceil($nombredElementsTotal / $limite);

/* Si on est sur la première page, on n'a pas besoin d'afficher de lien
 * vers la précédente. On va donc l'afficher que si on est sur une autre
 * page que la première */
if ($page > 1):
    ?><a href="?page=<?php echo $page - 1; ?>">Page précédente</a><?php
endif;

/* On va effectuer une boucle autant de fois que l'on a de pages */
for ($i = 1; $i <= $nombreDePages; $i++):
    ?><a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a> <?php
endfor;

/* Avec le nombre total de pages, on peut aussi masquer le lien
 * vers la page suivante quand on est sur la dernière */
if ($page < $nombreDePages):
    ?><a href="?page=<?php echo $page + 1; ?>">Page suivante</a><?php
endif;
?>

Génération des liens directs vers chaque page

Nous avons désormais notre pagination définitive : nous pouvons accéder à toutes les pages en un clic, nous pouvons passer d'une page à l'autre dans les deux "directions". Au niveau de l'expérience utilisateur nous avons :

  • organisé l'information en petites parties qu'il est plus simple pour l'utilisateur de parcourir ;
  • diminué le temps de chargement des pages ;
  • réparti un tant soit peu la charge en demandant le plus d'informations possible à MySQL plutôt que de tout faire en PHP ;
  • minimisé l'empreinte mémoire nécessaire

Le jeu en valait donc la chandelle, non ?  :)


Pour terminer, je vous remets la base définitive.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
$cnx = new PDO('mysql:host=localhost;database=my_database;charset=utf8', 'root', '');

$page = (!empty($_GET['page']) ? $_GET['page'] : 1);
$limite = 10;

// Partie "Requête"
$debut = ($page - 1) * $limite;
$query = 'SELECT CALC_FOUND_ROWS * AS `total` FROM `my_table` LIMIT :limite OFFSET :debut';
$query = $cnx->prepare($query);
$query->bindValue('debut', $debut, PDO::PARAM_INT);
$query->bindValue('limite', $limite, PDO::PARAM_INT);
$resultSet = $query->execute();

$resultFoundRows = $cnx->query('SELECT found_rows()');
$nombredElementsTotal = $resultFoundRows->fetchColumn();

// Partie "Boucle"
while ($element = $resultSet->fetch()) {
    // C'est là qu'on affiche les données  :)
}

// Partie "Liens
$nombreDePages = ceil($nombredElementsTotal / $limite);
if ($page > 1):
    ?><a href="?page=<?php echo $page - 1; ?>">Page précédente</a><?php
endif;
for ($i = 1; $i <= $nombreDePages; $i++):
    ?><a href="?page=<?php echo $i; ?>"><?php echo $i; ?></a> <?php
endfor;
if ($page < $nombreDePages):
    ?><a href="?page=<?php echo $page + 1; ?>">Page suivante</a><?php
endif;

Une pagination "complète" en PHP et MySQL

Comme vous pouvez le constater, cela ne coule pas nécessairement de source, et il y a pas mal de paramètres dont il faut tenir compte. Mais maintenant, vous avez des exemples et donc de bonnes bases pour réaliser vos propres paginations.

Dans un premier temps, vous pouvez vous soucier de l'avertissement donné plus haut : si l'on met dans le paramètre page de l'URL un nombre négatif, à virgule, plus grand que le nombre total de pages, ou même quelque chose qui n'est pas un nombre, ça peut poser problème. Ou alors malgré un nombre respectable d'éléments par page, vous avez trop de pages, et vous souhaitez n'afficher des liens que vers certaines d'entre-elles, comme la première et la dernière, ainsi que les trois précédentes et les trois suivantes…

Vous pouvez encore imaginer un système avec de l'AJAX pour charger les éléments suivants comme sur les réseaux sociaux, voire – soyons fous ! — paginer des résultats de recherche en utilisant la fonction http_build_query() à bon escient…

Bref, une fois de plus, ce tutoriel vous permet de comprendre la réflexion qu'il y a derrière la pagination afin de pouvoir mettre en place une version très simple, mais néanmoins utilisable et pratique. La suite n'appartient qu'à vous.

À vos claviers !

8 commentaires

Une petite question : sachant que je veux ordonner mes résultats par ordre alphabétique; si je fais quelque chose du genre :

1
SELECT * FROM table ORDER BY NameChamp  ASC LIMIT 0,10

Est ce que le ORDER BY s'applique sur l'ensemble de la table ou sur les résultats renvoyés? Dit autrement, est-ce que je récupère les 10 premiers éléments puis ils sont mis par ordre alphabétique, ou autre contre l'ordre alphabétique s'applique sur la table puis je récupère les 10 premiers éléments une fois le classement effectué?

Merci ;)

"Il est vraiment regrettable que tous les gens qui savent parfaitement comment diriger un pays soient trop occupés à conduire des taxis et à couper des cheveux"

+0 -0

C'est la seconde solution, à savoir que la clause de tri (ORDER BY) est appliquée avant la clause de restriction (LIMIT). Il est donc ainsi possible de paginer des résultats triés.

Evitez qu'on vous dise de les lire : FAQ PHP et Symfony 2Tutoriel WAMP • Cliquez 👍 pour dire merci • Marquez vos sujets résolus

+2 -0

Merci pour ce tutoriel.

Une petite astuce qui peut éviter quelques problèmes :

1
2
3
4
5
6
<?php
$page = intval($_GET['page']); // Conversion forcée en entier
// Si le nombre est invalide, on demande la première page par défaut
if($page <= 0) {
    $page = 1;
}

J'ai survolé le reste et ça m'a l'air très bien construit et expliqué. :)

Peut-être préciser qu'il ne faut pas reproduire le comportement "connexion à MySQL avec root" en production (si si, ça arrive), mais comme c'est pour la beauté de l'exemple, c'est du pinaillage… ^^

Du beau boulot au final.

+0 -0

Merci. Je sais maintenant utiliser OFFSET et LIMIT efficacement. ;)

Et sinon tu n'aurais pas envie de faire un tuto sur << l'infinite scroll >> ? La technique en elle même me paraît pas si compliqué. Mais au niveau du référencement, je me demande comment on peut optimiser l'affichage du contenu ? J'avais pensé à des pré-liens cachés pour que le robot d'indexation contourne ce problème. Bonne ou mauvaise idée ? :)

Tant de choses, tant de vies, tant de possibilités.

+0 -0

Et sinon tu n'aurais pas envie de faire un tuto sur << l'infinite scroll >> ?

Yarflam

C'est un des trucs listés dans la conclusion, quand je parle du chargement par AJAX  ;)
J'ai hésité à en parler ici, mais cela me paraissait être quelque chose de trop particulier qui n'entrait pas vraiment dans le champ du tutoriel tel que je le voyais.

Mais au niveau du référencement, je me demande comment on peut optimiser l'affichage du contenu ? J'avais pensé à des pré-liens cachés pour que le robot d'indexation contourne ce problème. Bonne ou mauvaise idée ? :)

Yarflam

Non, pas tellement, à mon avis. Moi je mettrais une pagination "normale" que je modifierais en JavaScript. Ça a l'avantage SEO et accessibilité.

Pour l'anecdote, un client a une fois vu qu'il y avait une pagination masquée, et m'a demandé à l'enlever, malgré mes conseils et protestations. Il est revenu vers moi quelques semaines plus tard pour me dire qu'il ne trouvait plus certains éléments avec un moteur de recherche. Quand je lui ai eu réexpliqué pourquoi je ne voulais pas qu'on enlève ça et que c'était une conséquence, il n'a étrangement plus rien dit… On a remis en place et dans les trois jours, les autres éléments étaient à nouveau accessibles.

Edit

J'aimerais juste préciser que j'entends "masquer ou changer le contenu du conteneur de la pagination" par "modifier"  ;)

Édité

Evitez qu'on vous dise de les lire : FAQ PHP et Symfony 2Tutoriel WAMP • Cliquez 👍 pour dire merci • Marquez vos sujets résolus

+1 -0

Non, pas tellement, à mon avis. Moi je mettrais une pagination "normale" que je modifierais en JavaScript. Ça a l'avantage SEO et accessibilité.

Ymox

Pas bête. Ta solution est bien meilleure, merci. :)

En plus, c'est tout simple à réaliser. Si on se trouve sur http://monsite.fr/page-4, on affiche avec AJAX les pages 1/2/3/5 et on scroll jusqu'à la position 4. Voir même on cache les pages 1 & 2 pour ne pas surcharger le scroll (comme sur FB pour les années).

Édité

Tant de choses, tant de vies, tant de possibilités.

+0 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte