Gestion d’un système de pagination

On a toujours, à un moment où à un autre, besoin de gérer un affichage sur plusieurs pages. L’exercice n’est pas réellement complexe, mais il faut ruser pour éviter de lister un nombre interminable de liens.

Gérer un système de pagination n’est pas véritablement complexe, mais il demande une certaine gymnastique pour éviter les listes interminables de liens, lorsqu’on gère de grands nombres de pages.
Nous allons essayer d’en gérer un de la manière la plus simple possible.

Affichage des données

J’utilise pour cet essai une simple table dans une base de données, listant des adresses d’images, pour gérer une galerie.
Nous commençons par initialiser quelques variables :

if(isset($_GET['page']) && is_numeric($_GET['page']))
    $page = $_GET['page'];
else
    $page = 1;
$nb_par_page = 10;
$debut = ($page-1)*$nb_par_page;

La variable page est initialisée à 1 si elle n’existe pas, on choisit ensuite le nombre de résultats par page, puis on détermine l’index du résultat qui nous intéresse pour la requête SQL (si on est en page 1, on cherchera à partir de l’index 0, si on est en page 2 à partir de l’index 10, etc).
On effectue ensuite la requête proprement dite :

$sql = "SELECT SQL_CALC_FOUND_ROWS Url 
        FROM items LIMIT $debut, $nb_par_page";
$req = mysql_query($sql) or die(mysql_error());

Avec une particularité, l’ajout de SQL_CALC_FOUND_ROWS, qui permet de calculer le nombre de lignes qu’aurait retourné la requête si on n’avait pas utilisé de clause LIMIT. On récupère cette information dans une requête ultérieure, avec un simple « SELECT FOUND_ROWS()« .

$nb_results = mysql_result(mysql_query("SELECT FOUND_ROWS()"), 0);
$nb_pages = ceil($nb_results/$nb_par_page);

Il ne nous reste plus qu’à afficher les résultats proprement dits.

if(mysql_num_rows($req)) {
    echo '<ul id="liste">';
    while($row = mysql_fetch_assoc($req))
        echo '<li><a href="img/'.$row['Url'].'">
              <img src="img/mini/'.$row['Url'].'"></a></li>';
    echo '</ul>';
}

La pagination

Le plus simple étant fait, attaquons dès à présent le coeur du dossier : comment gérer la pagination ? Le plus simple serait de faire une boucle, de la première à la dernière page, mais cette solution trouve très vite ses limites dès qu’on commence à avoir un nombre conséquent de pages. Il va falloir trouver un moyen de limiter cet affichage.
Nous commençons par créer la fonction de pagination (Nous ferons en sorte qu’elle soit réutilisable au maximum). Les seuls paramètres qui soient utiles sont le numéro de page actuel, et le nombre total de pages.

function pagination($page, $total_pages) {
}

Je pars du principe qu’il ne faut afficher que le numéro de la première page, le dernier, et un intervalle de deux numéros autour de la page actuelle (soit deux pages avant et deux pages après). Les trous restant sont remplacés par « […]« .

$autour = 2;
$intervalle = '[...]';
$html = '<div class="pagination">'; 
$debut = $fin = false; 
$tab_autour = array(); 
for($i = $page-$autour; $i <= $page+$autour; $i++) 
    $tab_autour[] = $i;

Je construis l’array $tab_autour de manière à ce qu’il comporte le numéro de la page, ainsi que les numéros qui sont autour. Ainsi, il me suffira de vérifier lors de la boucle que le numéro de page est dans cet array pour décider de l’afficher ou pas. Même s’il comporte des numéros négatifs ou trop élevés, ce ne sera pas grave. Il nous reste à construire la boucle :

for($j = 1; $j <= $total_pages; $j++) {
}

Il faut ensuite remplir cette boucle, en n’affichant que les résultats voulus :

if($j == $page)
     $html .= $j.' ';
elseif($j == 1 || $j == $total_pages || in_array($j, $tab_autour))
     $html .= '<a href="?page='.$j.'">'.$j.'</a> ';

Le numéro de page actuel est affiché sans lien, et les numéros de pages désirés sont affichés également. Il ne reste qu’à afficher notre petit « […] » dans les intervalles restants :

elseif($j <$page && !$debut) {
     $html .= $intervalle.' ';
     $debut = true;
}
elseif($j > $page && !$fin) {
     $html .= $intervalle.' ';
     $fin = true;
}

On met les variables $debut et $fin à true afin de n’obtenir qu’un seul […].
Il ne nous reste plus qu’à renvoyer le code généré, et tout est désormais fonctionnel.

$html .= '<div>';
return $html;