Construire un menu arborescent avec une fonction récursive en PHP
Un sujet assez difficile à comprendre dans le monde de la programmation web pour plusieurs webmasters qui ont appris par eux-mêmes, dont je fais parti, est la technique de récursivité, où une fonction fait appel à elle-même. Cette fonction est dit récursive dans ce cas, et elle peut être nécessaire dans plusieurs situations, telles que la création d’un menu arborescent complexe où le nombre de sous-catégories n’est pas prévisible.
Commençons par la structure d’une base de donnée MySQL simple et typique pour ces catégories. Celle-ci est une table, appelée Animaux, contenant une liste de catégories, avec leur identitification propre, leur nom, et leur lien de parenté avec une autre catégorie, si elle est en fait une sous catégorie de celle-ci dans une menu arborescent.
ID Parent Nom 1 0 Félins 2 1 Grands Félins 3 2 Panthères 4 2 Lions 5 1 Petits Félins 6 2 Tigres 8 0 Poissons 9 0 Canins 10 8 Saumons 11 8 Requins 12 9 Loups 13 9 Chiens
Le champs Parent d’une sous-catégorie fait référence à l’identification ID de la catégorie dont elle fait partie. Par exemple, Panthères, avec la valeur Parent de 2, est une sous-catégorie de Grands Félins, identifiée par le code ID 2, et avec son code Parent de 1, elle est elle-même une sous-catégorie Félins, identifiée par le code ID 1. Comme Félins est une catégorie principale qui n’a pas de lien de parenté, sa valeur pour Parent est 0.
Pour saisir ces données et l’insérer dans une liste (array en anglais) afin de pouvoir l’organiser avec PHP, on fera appel à la fonction suivante:
1 2 3 4 5 6 7 8 9 10 11 12 | $query = "SELECT ID, Parent, Nom FROM Animaux ORDER BY Nom ASC"; $result = mysql_query($query); $categories = array(); while($row = mysql_fetch_array($result)) { $categories[] = array( 'parent_id' => $row['Parent'], 'categorie_id' => $row['ID'], 'nom_categorie' => $row['Nom'] ); } |
Si on visualise les 3 premiers éléments de la liste associative en PHP qui est maintenant en ordre alphabétique, elle ressemble à ça:
( [0] => Array ( [parent_id] => 1 [categorie_id] => 5 [nom_categorie] => Petits Félins ) [1] => Array ( [parent_id] => 9 [categorie_id] => 13 [nom_categorie] => Chiens ) [2] => Array ( [parent_id] => 8 [categorie_id] => 10 [nom_categorie] => Saumons ) )
Nous avons donc maintenant le contenu de notre table dans une liste PHP organisée en ordre alphabétique. Comment l’organiser de façon à ce que la liste de menu soit affichée en mode arborescent dans une page HTML? Nous allons utiliser une fonction récursive appelée afficher_menu(). Dans cette version, nous allons tout simplement l’afficher en mode texte de façon à rester simple et compréhensible à ce niveau du tutoriel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function afficher_menu($parent, $niveau, $array) { $html = ""; foreach ($array AS $noeud) { if ($parent == $noeud['parent_id']) { for ($i = 0; $i < $niveau; $i++) $html .= "-"; $html .= " " . $noeud['nom_categorie'] . "<br />"; $html .= afficher_menu($noeud['categorie_id'], ($niveau + 1), $array); } } return $html; } |
Quelques explications: Alors qu’on parcoure la liste désorganisée, si on rencontre une catégorie qui a un lien de parenté avec une autre, après l’avoir ajoutée dans le code HTML (variable $html), on fait encore appel à la même fonction afin de vérifier si celle-ci n’a pas aussi une sous-catégorie, avant de continuer les instructions additionnelles de la fonction, et ainsi de suite. On sauvegarde le nombre de fois que la fonction est appelée par elle-même dans la variable $niveau afin de déterminer combien de symboles « - » on devrait ajouter afin de démontrer sa position dans la structure arborescente.
Ensuite, nous faisons appel à cette fonction dans la page web ou nous voulons afficher le menu. Le premier 0 de la fonction pour la variable $parent indique que nous voulons afficher le menu en commençant avec les catégories principales à la racine qui ont une valeur de parenté de 0, mais au besoin, on pourrait seulement afficher une sous-catégorie avec une valeur de parenté différente. L’autre 0 initialise la valeur de $niveau à 0. $categories est évidemment notre liste précédente:
echo afficher_menu(0, 0, $categories);
Le résultat devrait être celui-ci:
Canins - Chiens - Loups Félins - Grands Félins -- Lions -- Panthères -- Tigres - Petits Félins Poissons - Requins - Saumons
Maintenant, ce format n’est pas très élégant, nous allons voir comment formater ce menu textuel avec les balises <ul> et <li>, une façon sémantiquement correcte de présenter une liste en HTML, et qui peut, par la suite, être pleinement contrôlée en CSS pour créer un menu de navigation esthétiquement plaisant.
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 | function afficher_menu($parent, $niveau, $array) { $html = ""; $niveau_precedent = 0; if (!$niveau && !$niveau_precedent) $html .= "\n<ul>\n"; foreach ($array AS $noeud) { if ($parent == $noeud['parent_id']) { if ($niveau_precedent < $niveau) $html .= "\n<ul>\n"; $html .= "<li>" . $noeud['nom_categorie']; $niveau_precedent = $niveau; $html .= afficher_menu($noeud['categorie_id'], ($niveau + 1), $array); } } if (($niveau_precedent == $niveau) && ($niveau_precedent != 0)) $html .= "</ul>\n</li>\n"; else if ($niveau_precedent == $niveau) $html .= "</ul>\n"; else $html .= "</li>\n"; return $html; } |
Quelques explications: La variable $niveau_precedent garde en mémoire le niveau précédent dans l’arborescence du menu, donc on peut la comparer au niveau actuel, $niveau, pour voir s’il y a eu un changement pour contrôler les débuts et les fins de liste avec la balise ul. À la ligne 6, si la variable est encore vide, on commence avec la balise ul de la liste principale. Ensuite, à la ligne 12, si le niveau vient d’augmenter, on peut commencer avec une nouvelle balise ul qui sera un sous-menu. On enregistre la niveau actuel à la ligne 16, et la fonction est appelée récursivement pour voir s’il y aura encore un sous-menu. Dans le cas contraire, on doit contrôler la fermeture des balises correctement.
Cette fonction va générer le code suivant:
<ul> <li>Canins <ul> <li>Chiens</li> <li>Loups</li> </ul> </li> <li>Félins <ul> <li>Grands Félins <ul> <li>Lions</li> <li>Panthères</li> <li>Tigres</li> </ul> </li> <li>Petits Félins</li> </ul> </li> <li>Poissons <ul> <li>Requins</li> <li>Saumons</li> </ul> </li> </ul>
Pour ajouter des liens à chaque catégories, il suffit maintenant de modifier la ligne 14 avec celle-ci:
14 | $html .= "<li><a href=\"?categorie=" . $noeud['categorie_id'] . "\">" . $noeud['nom_categorie'] . "</a>"; |
Le résultat est le suivant:

Pour apprendre à faire un menu dynamique de façon à cacher les sous-catégories et les afficher quand on clique sur son menu parent, veuillez vous référez à l’article: Comment développer des sous-menus cachés.

Bonsoir,
Merci pour cette excellente démonstration ! c’est ce que je recherchais ;-)
Par contre, j’aimerai insérer tout ça dans une liste déroulante, merci de votre aide ! Eric
Salut Martin et merci encore pour ce tutoriel.
C’est bien fait et très clair.
Je cherche à créer une liste (code html) à partir d’une table de ma BDD afin de créer un menu dynamique.
Je suis tombé hier sur la gestion d’arbres par représentation intervallaire (http://sqlpro.developpez.com/cours/arborescence/).
Ça l’air pas mal du tout !!!
Je pense que ce principe est idéal pour manipuler facilement la structure du menu.
Le hic c’est de créer la fonction PHP qui créera ma liste en html.
Je sèche un peu je dois avouer…
Tu n’aurais pas une piste à me donner ?
Merci
Est-ce moi ou il y a une erreur concernant la fermeture des balises ?
Oh… Mille pardons… C’était bien moi…
Tu es un seigneur!!!
Je viens de passer des jours pour ne meme pas achever ma propre fonction recursive. T’es trop fort !
Une petite info: L’utilisation de variables non initialisées renvoient des notices:
(genre html .= ****)
Pour l’éviter j’ai mis au début de la fonction html:
if (!isset($html)) $html = « »;
if (!isset($previous_level)) $previous_level = « »;
Merci encore!
Merci rip_pit, en effet, il semble difficile de trouver de la doc simple sur ce sujet. Pour les notices, j’ai ajouté une déclaration des variables au début de la fonction, c’est plus propre ainsi. Comme certaines personnes m’ont aussi demandé de franciser un peu plus la fonction, j’en ai profité pour traduire quelques morceaux, donc certains noms de variables ont changé aujourd’hui, pour le reste tout est pareil.
Bonjour,
Je voudrais savoir si vous seriez d’accord d’atapter vous tuto ++http://www.coinduwebmaster.com/menu-arborescent-fonction-recursive-php/89/++ a mes besoins et cela contre rémunération évidament.
D’avance merci
Waw kel demo, kel tuto, felicitations, ur the best !!! :=)
Super, ca aide pas mal, surtout pour les petits développeur. une petite question. comment afficher le nombre de produits dans chaque catégorie. j’ai une table boutique_categories avec un champs id_cat. je n’arrive pas à compter le nbre. quelqun aurais un piste svp ?
Bonjour,
Merci pour le script. j’ai une question. est-il possible d’enlever le lien sur le rubrique qui a des sous rubriques ?
merci d’avance.
Bonjour à tous !!
Cela fait 2 jours que je cherche à finaliser un système comme celui-ci, mais avec aussi du code JS qui affiche et cache les liens sur click.
J’ai passé ma journée hier à chercher, et ce matin j’ai décidé de faire moi même mes propres boucles parce que c’est vraiment complexe cette (connerie^^) de récursivité.
Je viens de terminer, voila mes sources :
function afficher_menu()
{
// Requête SQL
$query = ( »SELECT ID, Parent, Nom FROM categories WHERE Parent=’0′ ORDER BY Nom ASC »); // Avec 0 on ne prend que les menus
$result = mysql_query($query);
$nb = 1;
while($row = mysql_fetch_array($result)) // la boucle va s’effectuer le nombre de titres trouvés dans la BDD
{
if ($nb != 1)
{
echo « \n »;
}
//Affichage du menu titre avec le lien pour ouvrir les sous menus
echo »« .$row['Nom']. »\n »;
$id = $row['ID'];
// Ecriture du script pour cacher le premier groupe dans le sous menu
echo « \n »;
// Requête pour afficher les sous-catégories
$query1 = ( »SELECT ID, Parent, Nom FROM categories WHERE Parent=’$id’ ORDER BY Nom »);
$result1 = mysql_query($query1);
while($row1 = mysql_fetch_array($result1))
{
$nom = $row1['Nom']; // Pour éviter le problème dans la boucle while suivante
$query2 = ( »SELECT ID FROM categories WHERE Nom=’$nom’ ORDER BY Nom »);
$result2 = mysql_query($query2);
while($row2 = mysql_fetch_array($result2))
{
//Affichage du lien du sous menu // POUR CHAQUE 1er sous menu // objectif : modifier ID pour avoir le lien 12 .. 13 ect
echo »« .$nom. »\n »;
////////////////////////// Fin des 2 premières catégories ////////////////////////
$IDRQ = $row2['ID'];
$nbl = 1;
// Requête pour afficher les derniers enfants
$query3 = ( »SELECT ID, Parent, Nom FROM categories WHERE Parent=’$IDRQ’ ORDER BY Nom »);
$result3 = mysql_query($query3);
while($row3 = mysql_fetch_array($result3))
{
if ($nbl == 1) // première fois qu’on passe donc on affiche le script pour cacher les lignes
{
// Paramètre ID identique à celui affiché juste au dessus sur le parent
echo « \n »;
}
// Affichage du lien pour la rubrique
echo »« .$row3['Nom']. »\n »;
$nbl++;
}
////////////////////////// Fin des 3 premières catégories ////////////////////////
}
}
$nb++;
}
}
Le script utilise la même base de donnée.
Et le il faut le script java :
function showmenu(menu) {
if (menu.style.display == ‘none’) menu.style.display = ‘block’;
else menu.style.display = ‘none’;
}
dans le header.
ENJOY !!!
Petite correction suite à un minuscule bug de fermeture de balises
à remplacer entre les 2 commentaires en fin de script :
////////////////////////// Fin des 2 premières catégories ////////////////////////
$IDRQ = $row2['ID'];
$nbl = 1;
// Requête de calcul pour l’affichage de la fermeture UL
$calcul=0;
$query4 = ( »SELECT ID, Parent, Nom FROM categories WHERE Parent=’$IDRQ’ ORDER BY Nom »);
$result4 = mysql_query($query4);
while($row4 = mysql_fetch_array($result4))
{
$calcul++;
}
// Requête pour afficher les derniers enfants
$query3 = ( »SELECT ID, Parent, Nom FROM categories WHERE Parent=’$IDRQ’ ORDER BY Nom »);
$result3 = mysql_query($query3);
while($row3 = mysql_fetch_array($result3))
{
if ($nbl == 1) // première fois qu’on passe donc on affiche le script pour cacher les lignes
{
// Paramètre ID identique à celui affiché juste au dessus sur le parent
echo « \n »;
}
// Affichage du lien pour la rubrique
echo »« .$row3['Nom']. »« ;
$calcul–;
if ($calcul == 0)
{
echo »";
}
echo »\n »;
$nbl++;
}
////////////////////////// Fin des 3 premières catégories ////////////////////////
Si je veux retourner un tableau imbriqué avec afficher_menu($parent, $niveau, $array), comment je m’y prends ? Il faut passer le tableau en paramètre dans la fonction récursive, utiliser un tableau temporaire ? Je cale ! Si vous pouvez m’éclairer, je dois avoir un problème de logique. Merci
Bonjour,
Merci pour cette source de Code, elle est vraiment impeccable.
cependant, j’ai un soucis à l’adapter pour être afficher dans un menu déroulant.
J’utilise la librairie « YUI Library » de Yahoo!. Pour voir un aperçu du menu voilà le lien :
http://developer.yahoo.com/yui/examples/menu/topnavfrommarkupwithanim_source.html
J’espère avoir bien expliquer mon Pb.
Merci d’avance.
Youpi !
Merci pour m’avoir fait découvrir la récursivité, enfin un exemple clair, ça change !
bon exemple pour la recursivité. Mais je ne recommande pas de l’utiliser tel quel, car il parcours le tableau avec une complexité en O(n2), en d’autres termes, il parcourt le tableau pour chaque élément du tableau. Pour afficher de telles données en arborescence, on a vu mieux au niveau performance (il y a des solutions avec lesquelles on ne parcourt que deux fois le tableau)…