Créer un menu php/sql

Eléphant du PHP | 231 Messages

07 mai 2010, 14:06

Bonjour,

Je souhaite créer un menu avec sous menu (infini) à partir d'une table sql.

Voici ma table et son contenu
--
-- Structure de la table `cms_menu`
--

CREATE TABLE IF NOT EXISTS `cms_menu` (
  `menu_id` mediumint(8) unsigned NOT NULL,
  `menu_parent_id` mediumint(8) unsigned NOT NULL,
  `menu_left_id` mediumint(255) unsigned NOT NULL,
  `menu_right_id` mediumint(255) unsigned NOT NULL,
  `menu_name` varchar(255) COLLATE utf8_bin NOT NULL,
  `menu_link` varchar(255) COLLATE utf8_bin NOT NULL,
  `menu_is_published` tinyint(1) NOT NULL,
  PRIMARY KEY (`menu_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

--
-- Contenu de la table `cms_menu`
--

INSERT INTO `cms_menu` (`menu_id`, `menu_parent_id`, `menu_left_id`, `menu_right_id`, `menu_name`, `menu_link`, `menu_is_published`) VALUES
(1, 0, 3, 5, 'Forum', '', 1),
(3, 0, 0, 1, 'Home', '', 1),
(4, 0, 5, 0, 'Cat 2', '', 1),
(5, 0, 1, 4, 'Cat 1', '', 1),
(7, 5, 9, 0, 'Sub Cat 1.2', '', 1),
(9, 5, 0, 7, 'Sub Cat 1.1', '', 1);

Et voici mon code php
$sql = 'SELECT *
		FROM `cms_menu`
		WHERE `menu_is_published` = 1';

$result = $db->sql_query($sql);

while($row = $db->sql_fetchrow($result))
{
	if( $row['menu_parent_id'] == 0 && $row['menu_left_id'] == 0 )
		$menu_id = $row['menu_id'];
	
	$a_menu[$row['menu_id']] = array( 'menu_value' => '<li><a href="' . $row['menu_link'] . '">' . $row['menu_name'] . '</a></li>', 'menu_right_id' => $row['menu_right_id'] );
}

echo '<ul>';

while( $menu_id != 0 ) //J'ai donné la valeur de 0 à menu_right_id si y'a plus de menu après
{
	echo $a_menu[$menu_id]['menu_value'];
	
	$menu_id = $a_menu[$menu_id]['menu_right_id'];
}

echo '</ul>';
Ceci fonctionne pour le premier niveau ... mais comment y inclure les sous niveaux de manière 'intelligente' ?

devlop78
Invité n'ayant pas de compte PHPfrance

07 mai 2010, 19:44

Si je me souviens bien j'avais utilisé une fonction récursive. En gros, le principe était de construire un tableau php représentant le menu pour après le parcourir et l'afficher (mais j'avais une colonne "hierarchie" qui m'aidait dans la tâche, me permettant de descendre la hierarchie petit à petit).

On peut imaginer des requêtes (il existe surêment plus optimisé mais là n'est pas la question).

Tu cherches tous ceux qui n'ont pas de parents. Tu les mets dans $tableau.
Pour chaque, tu cherches leurs enfants, que tu mets dans $tableau[parent] par exemple.
Pour chaque enfant, tu cherches les arrières enfants, que tu mets dans $tableau[parent][enfant] par exemple.

Et ce de manière infinie. Il est possible que la semaine prochaine j'en fasse un. Si tu n'as pas ta solution d'ici là, je pourrais partager mon code source.

a++

Eléphant du PHP | 231 Messages

07 mai 2010, 20:57

Si je me souviens bien j'avais utilisé une fonction récursive. En gros, le principe était de construire un tableau php représentant le menu pour après le parcourir et l'afficher (mais j'avais une colonne "hierarchie" qui m'aidait dans la tâche, me permettant de descendre la hierarchie petit à petit).
en cherchant j'ai pensé à ça aussi ... mais j'avais un doute :|

donc en modifiant la base
--
-- Structure de la table `cms_menu`
--

CREATE TABLE IF NOT EXISTS `cms_menu` (
  `menu_id` mediumint(8) unsigned NOT NULL,
  `menu_parent_id` mediumint(8) unsigned NOT NULL,
  `menu_left_id` mediumint(255) unsigned NOT NULL,
  `menu_right_id` mediumint(255) unsigned NOT NULL,
  `menu_level` tinyint(3) unsigned NOT NULL,
  `menu_name` varchar(255) COLLATE utf8_bin NOT NULL,
  `menu_link` varchar(255) COLLATE utf8_bin NOT NULL,
  `menu_is_published` tinyint(1) NOT NULL,
  PRIMARY KEY (`menu_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
Le contenu

Code : Tout sélectionner

-- -- Contenu de la table `cms_menu` -- INSERT INTO `cms_menu` (`menu_id`, `menu_parent_id`, `menu_left_id`, `menu_right_id`, `menu_level`, `menu_name`, `menu_link`, `menu_is_published`) VALUES (1, 0, 3, 5, 0, 'Forum', '', 1), (3, 0, 0, 1, 0, 'Home', '', 1), (4, 0, 5, 0, 0, 'Cat 2', '', 1), (5, 0, 1, 4, 0, 'Cat 1', '', 1), (7, 5, 9, 0, 1, 'Sub Cat 1.2', '', 1), (9, 5, 0, 7, 1, 'Sub Cat 1.1', '', 1);
Et le script
$sql = 'SELECT *
		FROM `cms_menu`
		WHERE `menu_is_published` = 1';

$result = $db->sql_query($sql);

while($row = $db->sql_fetchrow($result))
{
	if( $row['menu_parent_id'] == 0 && $row['menu_left_id'] == 0 )
		$menu_id = $row['menu_id'];
	
	if( $row['menu_level'] == 0 )
	{
		$a_menu[$row['menu_id']] = array( 
			'menu_value'	 => '<li><a href="' . $row['menu_link'] . '">' . $row['menu_name'] . '</a></li>',
			'menu_right_id'	 => $row['menu_right_id'],
			'menu_left_id'	 => $row['menu_left_id'],
		);
	}
	else
	{
		$a_menu[$row['menu_parent_id']]['child'][$row['menu_id']] = array( 
			'menu_value'	 => '<li><a href="' . $row['menu_link'] . '">' . $row['menu_name'] . '</a></li>',
			'menu_right_id'	 => $row['menu_right_id'],
			'menu_left_id'	 => $row['menu_left_id'],
		);
		
		if( $row['menu_left_id'] == 0 )
		{
			$a_menu[$row['menu_parent_id']]['first_child'] = $row['menu_id'];
		}
	}
}

echo '<ul>';

while( $menu_id != 0 )
{
	echo $a_menu[$menu_id]['menu_value'];
	
	if( isset( $a_menu[$menu_id]['first_child'] ) )
	{
		$child_menu_id = $a_menu[$menu_id]['first_child'];
		
		echo '<ul>';
		while( $child_menu_id != 0 )
		{
			echo $a_menu[$menu_id]['child'][$child_menu_id]['menu_value'];
			$child_menu_id = $a_menu[$menu_id]['child'][$child_menu_id]['menu_right_id'];
		}
		echo '</ul>';	
	}
	
	$menu_id = $a_menu[$menu_id]['menu_right_id'];
}

echo '</ul>';
ça m'affiche bien :
  • Home
  • Forum
  • Cat 1
    • Sub Cat 1.1
    • Sub Cat 1.2
  • Cat 2
Par contre je vois pas comment rendre ça récursif à l'infini #-o j'ai du mal aujourd'hui :oops:

devlop78
Invité n'ayant pas de compte PHPfrance

09 mai 2010, 01:30

Voilà, j'ai fini mon petit script. J'ai pas trouvé ça super facile, mais bon avec de la reflexion, on y arrive.

Soit une table avec 'id', 'parent' et 'nom' qui sont respectivement les id, les id des parents, et le nom des catégories.
Soit $resultat un tableau de type $tableau[lignes][colonnes] donc par exemple $resultat[0]['nom'] donne le nom du premier résultat de MySql.

Code : Tout sélectionner

recursiveSearch($resultat, $categories, 0); // On envoit la base 0 qui est "aucune catégorie" function recursiveSearch (&$resultat, &$categories, $id) { // On récupère $resultat (pas en global car destiné à une classe), l'identifiant du papa foreach ($resultat as $value) { // On parcours le tableau if ($value['parent'] == $id) { // Si la ligne trouvée possède comme parent l'identifiant alors ça nous intéresse $categories[$value['id']]['nom'] = $value['nom']; // On la rajoute comme enfant, avec son nom et son URL. $categories[$value['id']]['url'] = '#null'; recursiveSearch($resultat,$categories[$value['id']],$value['id']); // On lance la recherche pour voir si il n'y a pas d'enfants à charge (vive la pension alimentaire) } } } var_dump($categories); // On voit ce que ça donne
array(3) {
[1]=>
array(3) {
["nom"]=>
string(19) "categorie parente 1"
["url"]=>
string(1) "#"
[3]=>
array(2) {
["nom"]=>
string(21) "Sous Categorie 1 de 1"
["url"]=>
string(1) "#"
}
}
[2]=>
array(4) {
["nom"]=>
string(19) "categorie parente 2"
["url"]=>
string(1) "#"
[4]=>
array(3) {
["nom"]=>
string(21) "Sous categorie 1 de 2"
["url"]=>
string(1) "#"
[6]=>
array(2) {
["nom"]=>
string(21) "Sous categorie 1 de 4"
["url"]=>
string(1) "#"
}
}
[7]=>
array(2) {
["nom"]=>
string(21) "Sous categorie 2 de 2"
["url"]=>
string(1) "#"
}
}
[5]=>
array(2) {
["nom"]=>
string(19) "Categorie parente 3"
["url"]=>
string(1) "#"
}
}

Eléphant du PHP | 231 Messages

09 mai 2010, 09:58

Je comprends pas trop ton code il manque la partie table sql / récupération des données pour savoir à quoi correspond résultat (là je vois pas trop en fait) :roll: et surtt je pense pas que ça soit super optimisé à passer par un foreach à chaque tour au lieu de garder les relations left/right id :?

Sans changer le début (construction du tableau) j'ai modifié comme ceci mon bout de script
echo '<ul>';

while( $menu_id != 0 )
{
	echo $a_menu[$menu_id]['menu_value'];
	
	recursiveSearch( $a_menu, $menu_id );
	
	$menu_id = $a_menu[$menu_id]['menu_right_id'];
}

echo '</ul>';

function recursiveSearch( $a_menu, $menu_id )
{
	if( isset( $a_menu[$menu_id]['first_child'] ) )
	{
		$child_menu_id = $a_menu[$menu_id]['first_child'];
		
		echo '<ul>';
		while( $child_menu_id != 0 )
		{
			echo $a_menu[$menu_id]['child'][$child_menu_id]['menu_value'];
			
			if( isset( $a_menu[$child_menu_id]['first_child'] ) )
			{
				recursiveSearch( $a_menu, $child_menu_id );
			}
			
			$child_menu_id = $a_menu[$menu_id]['child'][$child_menu_id]['menu_right_id'];
			
		}
		echo '</ul>';	
	}	
}
J'ai rajouté un niveau et ça à l'air de fonctionner puisque j'obtiens bien ce résultat :
  • Home
  • Forum
  • Cat 1
    • Sub Cat 1.1
      • Sub Cat 1.1.1
    • Sub Cat 1.2
  • Cat 2
Par contre je me sert de la colonne 'level' juste pour déterminer si c'est de niveau 0 (c'est à dire catégorie principal) ou différent de 0 c'est une sous catégorie.

Reste à pousser les tests, et à implémenter une classe autour de ça pour ajouter / supprimer / modifier / changer de place ... des menus

devlop78
Invité n'ayant pas de compte PHPfrance

11 mai 2010, 01:03

Dans ma table j'ai trois colonnes : id, parent et nom. Un quatrième peut être url mais pour l'instant je ne l'ai pas mis (on verra si j'en ai besoin).

Par la suite, viendra une fonction qui y rajoute les articles, puis une qui formate (et là c'est la view qui s'en occupe et plus le model) pour mettre les <ul>, les <li> et les classes css (notamment la classe qui indique quel élément est actuellement concerné).

La seule question que je me pose c'est comment faire exactement pour la table des articles pour éviter d'utiliser trop de performances : mettre des index, séparer les ensemble id_articles/url_articles/id_categories de id_articles/contenu, etc, ou est-ce que Mysql gérera très bien ma requête SELECT si je ne lui demande pas le champ "contenu". A part ça, limiter les foreach pour moi ça veut dire créer un champ niveau (level), en précisant si c'est un niveau 0, 1 (enfant), 2 (sous-enfant), etc. Mais là je préfère comme c'est actuellement : peu de lignes de codes pour un résultat "puissant".