Arbre en base de données
Par Pierrick, mercredi 11 mai 2005 à 22:26 / categorie: Développement / tags: développement, PhpWebGallery / #18 / rss
Suite à la lecture extrêment intéressante du billet Stockage d'arbres dans une base de données sur le blog de Satz, proposant 2 modèles pour stocker un arbre en base de données, je me suis dit qu'il pourrait être intéressant de parler un peu de la méthode utilisée dans PhpWebGallery.
Le pré-requis à la lecture de ce billet est la lecture du billet de Satz (précédemment lié dans ce billet). Nous voilà donc avec 2 méthodes : liste adjacente (nommons la M1) et liste imbriquée (M2). Je ne connaissais pas M2 et je vais peut-être réfléchir (avec l'équipe de dev) pour déterminer si son implémentation dans PhpWebGallery est pertinente. En effet, PhpWebGallery utilise M1, avec une bonne dose d'optimisation.
Stockage dans la base
+----+----------------+-------------+-----------+ | id | name | id_uppercat | uppercats | +----+----------------+-------------+-----------+ | 1 | vacances | NULL | 1 | | 2 | mer | 1 | 1,2 | | 3 | méditerranée | 2 | 1,2,3 | | 4 | atlantique | 2 | 1,2,4 | | 5 | montagne | 1 | 1,5 | | 6 | alpes | 5 | 1,5,6 | | 7 | pyrénées | 5 | 1,5,7 | +----+----------------+-------------+-----------+C'est donc M1 avec le lien direct entre la catégorie et sa catégorie parente (
id_uppercat). Et juste à côté, la colonne uppercats donne le chemin complet de la racine vers la catégorie elle-même sous forme d'une liste d'identifiants numérique séparés par des virgules. Cette colonne est le résultat d'un calcul (du parcours de l'arbre très précisément) et peut être vidée/reconstruite à tout moment en théorie. Cette colonne est renseignée lors de la création de la ligne (en fonction de l'uppercats de la catégorie parente).
Utilisation
Je n'aime pas tellement les données calculées en base, mais il faut avouer que ça simplifie parfois énormément les traitements sans parler du gain de performance !- liste des catégories filles directement
SELECT id FROM categories WHERE id_uppercat = 1 ;
- liste des catégories filles récursivement (mais sans utiliser la coûteuse récursivité). Je ne suis pas sûr du tout que cette requête fonctionne avec un autre SGBD que MySQL. Mais alors, quelle puissance pour un coût très faible.
SELECT id FROM categories WHERE uppercats REGEXP '(^|,)1(,|$)' ;
- liste des catégories parentes jusqu'à la racine (sélection de la colonne
uppercatstout simplement)
Présentation de l'arborescence
Une arborescence, aussi profonde soit-elle, est affichable dans le menu des catégories. Encore une fois, point de récursivité, même si l'on souhaite présenter simultanément plusieurs niveaux de profondeur. Les 2 fonctions clefs sont get_categories_menu et get_html_menu_category.
Avant de présenter ces 2 fonctions, il faut présenter la colonne global_rank. A chaque catégorie est potentiellement associée une liste de sous-catégories. Ces sous-catégories sont ordonnées selon la colonne rank. Seulement voilà, notamment avec la branche 1.4, il a été nécessaire de pouvoir ordonner une liste de catégories qui ne sont pas forcément dans la même catégorie parente. C'est notamment le cas dans le formulaire à 2 tableaux des options globales des catégories, conçu par Gweltas (et selon moi, c'est une des meilleures évolutions ergonomiques de la branche 1.4). Le global_rank est donc équivalent à l'uppercats sauf que les identifiants des catégories sont remplacés par leurs rank respectifs. Le tri naturel (fonction strnatcasecmp en PHP) s'occupe du tri logique :-)
-
La fonction
get_categories_menu(include/functions_category.inc.php) s'occupe de récupérer les catégories à afficher dans le menu, de les trier puis de les passer à la fonctionget_html_menu_category(include/functions_html.inc.php).<?php function get_categories_menu() { global $page,$user; $infos = array(''); $query = ' SELECT name,id,date_last,nb_images,global_rank FROM '.CATEGORIES_TABLE.' WHERE 1 = 1'; // stupid but permit using AND after it ! if (!$user['expand']) { $query.= ' AND (id_uppercat is NULL'; if (isset ($page['tab_expand']) and count($page['tab_expand']) > 0) { $query.= ' OR id_uppercat IN ('.implode(',',$page['tab_expand']).')'; } $query.= ')'; } if ($user['forbidden_categories'] != '') { $query.= ' AND id NOT IN ('.$user['forbidden_categories'].')'; } $query.= ' ;'; $result = pwg_query($query); $cats = array(); while ($row = mysql_fetch_array($result)) { array_push($cats, $row); } usort($cats, 'global_rank_compare'); return get_html_menu_category($cats); }
-
Ci-dessous la fonction
get_html_menu_categorysimplifiée pour ce billet pour les besoins de l'exemple.Ce qui est intéressant, c'est que la profondeur est calculé à partir du nombre de séparateurs dans le
global_rank. Les écarts de profondeur entre chaque itération détermine s'il faut ouvrir une nouvelle sous-liste et combien il faut en fermer. Et oui, il est parfois utile d'en fermer plusieurs à la fois. Dans l'exemple plus haut, après la catégorie pyrénées il faudra fermer 2 listes.<?php /** * returns the HTML code for a category item in the menu (for category.php) * * HTML code generated uses logical list tags ul and each category is an * item li. The paramter given is the category informations as an array, * used keys are : id, name, nb_images, date_last * * @param array categories * @return string */ function get_html_menu_category($categories) { global $page, $lang; $ref_level = 0; $menu = ' <ul class="menu">'; foreach ($categories as $category) { $level = substr_count($category['global_rank'], '.'); if ($level > $ref_level) { $menu.= ' <ul class="menu">'; } else if ($level < $ref_level) { // we may have to close more than one level at the same time... $menu.= str_repeat("\n</ul>",($ref_level-$level)); } $ref_level = $level; $menu.= ' <li>'; $url = add_session_id(PHPWG_ROOT_PATH.'category.php?cat='.$category['id']); $menu.= '<a href="'.$url.'" '.$category['name'].'</a>'; $menu.= '</li>'; } $menu.= ' </ul>'; return $menu; }
-
Pour être tout à fait complet, il faut ajouter
<?php function global_rank_compare($a, $b) { return strnatcasecmp($a['global_rank'], $b['global_rank']); }
Les exemples de code sont tirés de la release 1.4.0 de PhpWebGallery
Commentaires
Aucun commentaire pour le moment.
Ajouter un commentaire
Les commentaires pour ce billet sont fermés.