Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [skrypt] menu drzewiaste
Forum PHP.pl > Inne > Oceny
sazian
Witam,

Ostatnio stanąłem przed problemem stworzenia menu drzewiastego. Sporo czytałem na ten temat i znalazłem wiele ciekawych artykułów na ten temat, ale autorzy tych artykułów skupiali się głównie na rozwiązaniach typu „drzewo Depesza”. Po krótkim zastanowieniu doszedłem do wniosku że raczej nie potrzebuję aż tak złożonego systemu. A skoro tak to jedyne rozwiązanie jakie mi pozostaje to standardowe id | id rodzica. Ale takie rozwiązanie wymaga wielu zapytań co jest nieefektywne, a alternatywą są tak na dobrą sprawę tylko drzewa które są skomplikowane w zarządzaniu.
Po zastanowieniu przyszło mi do głowy zasadnicze pytanie: „jak często jest aktualizowane menu?”. No właśnie we wszystkich przypadkach w których to rozwiązanie ma być wykorzystane pewnie nie częściej niż raz na miesiąc – pomijając fazę wdrażania systemu. A skoro tak to poco za każdym razem wykonywać te same czasochłonne zapytania skoro można zrobić cache.
Osobiście widzę tu dwie możliwości:
1) Zapisanie do plików każdej możliwej kombinacji układu menu – zapis wraz z kodem HTML, a później tylko wczytanie pliku
2) Zapis do pliku struktury bazy (zserializowana tablica), a następnie dynamiczne tworzenie menu na podstawie tej tablicy
Zdecydowałem się na rozwiązanie drugie – chociaż coraz intensywniej myślę na pierwszym wink.gif

Na schemacie poniżej widać przykładowe drzewo, gdzie kategoria „M_1_3” jest aktualnie wybraną kategorią, a kolorem zielonym zaznaczono gałęzie które muszą być widoczne.


A to kod – tagi HTML'a umieściłem dla uproszczenia, w „normalnej wersji” oczywiście jest użyty system szablonów.

  1. <?php
  2.  
  3. class Menu
  4. {
  5. /**
  6.   * struktura menu
  7.   * @var array
  8.   */
  9. protected $dane = null;
  10.  
  11. /**
  12.   * Potrzebne w wersji roboczej do pobrania struktury
  13.   * @var mysqli
  14.   */
  15. public static $db;
  16.  
  17. /**
  18.   * tworzy plik cache
  19.   **/
  20. public function cache()
  21. {
  22. $query = Menu::$db->query('SELECT kategoriaId, kategoriaZalezy, kategoriaNazwa, kategoriaPozycja FROM kategorie WHERE kategoriaAktywna = 1 ');
  23. if($query->num_rows > 0)
  24. {
  25. $tmp = array();
  26. $dane = array();
  27. while($row = $query->fetch_object())
  28. {
  29. $tmp[$row->kategoriaId] = array('id' => $row->kategoriaId, 'zalezy' => $row->kategoriaZalezy);
  30. $dane[$row->kategoriaId] = array('this_level' => array()//Zawiera identyfikatory kategorii mających tego samego rodzica – są na tym samym poziomie
  31. , 'parent' => $row->kategoriaZalezy//id rodzica
  32. , 'children' => array()// identyfikatory potomnych
  33. , 'nazwa' => $row->kategoriaNazwa
  34. , 'pozycja' => $row->kategoriaPozycja//Kolejność w grupie
  35. );
  36. }
  37.  
  38. foreach($tmp as $key => $value)
  39. {
  40. foreach($dane as $key2 => $value2)
  41. {
  42. if($key != $key2)
  43. {
  44.  
  45. if($value2['parent'] == $value['zalezy'])
  46. {
  47. $dane[$key2]['this_level'][] = $key;
  48. }
  49. if($value['zalezy'] == $key2)
  50. {
  51. $dane[$key2]['children'][] = $key;
  52. }
  53. }
  54. }
  55. }
  56. unset($tmp);
  57. file_put_contents('cache/menu.cache', serialize($dane));
  58. }
  59. }
  60.  
  61. /**
  62.   * pobiera plik cache, jeśli nie ma tworzy go
  63.   **/
  64. public function getCache()
  65. {
  66. if(!file_exists('cache/menu.cache'))
  67. {
  68. $this->cache();
  69. }
  70. $this->dane = unserialize(file_get_contents('cache/menu.cache'));
  71.  
  72. return $this->dane;
  73. }
  74.  
  75.  
  76. /**
  77.   * Wyświetla jeden poziom „potomny” - na schemacie 'M_1_3_1' i 'M_1_3_2'
  78.   * @param $id - id kategorii
  79.   **/
  80. public function drukuj_poziom($id)
  81. {
  82.  
  83. if($this->dane == null)
  84. {
  85. $this->getCache();
  86. }
  87. $ids = $this->dane[$id]['this_level'];
  88. $ids[] = $id;
  89. $tmp = array();
  90. foreach($this->dane as $key => $value)
  91. {
  92.  
  93. if(in_array($key, $ids) || ($id == 0 && $value['parent'] == 0))
  94. {
  95.  
  96. $tmp[$value['pozycja']] = '<li>'.$value['nazwa'].'</li>';
  97. }
  98. }
  99. ksort($tmp);//sortuje po kategoriaPozycja
  100. $r = implode('', $tmp);
  101. if($r != '')
  102. {
  103. return '<ul>'. $r.'</ul>';
  104. }
  105. else
  106. {
  107. return false;
  108. }
  109. }
  110. /**
  111.   * Wyświetla całe menu dla zaznaczonej kategorii - generowane rekurencyjnie
  112.   * @param $id - id aktywnej kategorii
  113.   * @param $text - "kod html" kategorii wyższej - Generowane automatycznie przy wywołaniu rekurencyjnym
  114.   * @param $poziom - Poziom zagnieżdżenia menu - Generowane automatycznie przy wywołaniu rekurencyjnym
  115.   **/
  116. protected function printHTML($id=0, $text = '', $poziom = 0){
  117. if($this->dane == null)
  118. {
  119. $this->getCache();
  120. }
  121. $poziom++;
  122.  
  123.  
  124. $ids = $this->dane[$id]['this_level'];
  125. $ids[] = $id;
  126. $tmp = array();
  127. $i=0;
  128. foreach($this->dane as $key => $value)
  129. {
  130.  
  131. if(in_array($key, $ids)|| ($id == 0 && $value['parent'] == 0))
  132. {
  133. $i++;
  134.  
  135. $tmp[$value['pozycja']] = '<li>'.$value['nazwa'].'</li>';
  136.  
  137.  
  138. if($id == $key && $text != '')
  139. {
  140.  
  141. $tmp[$value['pozycja']] .='<li>'.$text.'</li>';
  142.  
  143. }
  144. if($id == $key && $poziom == 1 && isset($this->dane[$id]['children'][0]))
  145. {
  146. $tmp[$value['pozycja']].='<li>'.$this->drukuj_poziom($this->dane[$id]['children'][0]).'</li>';
  147.  
  148. }
  149. }
  150. }
  151.  
  152.  
  153. ksort($tmp);
  154.  
  155. $r = implode('', $tmp);
  156. if($r != '' )
  157. {
  158. $r = '<ul>'. $r.'</ul>';
  159.  
  160. }
  161.  
  162.  
  163. if($this->dane[$id]['parent'] > 0)
  164. {
  165. $r = $this->printHTML($this->dane[$id]['parent'], $r, $poziom);
  166.  
  167. }
  168.  
  169. return $r;
  170.  
  171. }
  172. /**
  173.   * Wyświetla całe menu dla zaznaczonej kategorii - nakładka na printHTML
  174.   * @param $id - id aktywnej kategorii
  175.   **/
  176. public function treeHTML($id=0)
  177. {
  178.  
  179. return $this->printHTML($id);
  180. }
  181.  
  182. }
  183.  
  184. $db_host = '';
  185. $db_name = '';
  186. $db_password = '';
  187. $db_user = '';
  188.  
  189. $db = new mysqli($db_host, $db_user, $db_password, $db_name);
  190. $db->query('SET NAMES utf8');
  191. $db->query('SET CHARACTER SET utf8_general_ci');
  192. Menu::$db=$db;
  193.  
  194. $menu = new Menu();
  195. echo $menu->treeHTML(4);
  196. ?>
  197.  

przykładowa baza
  1. CREATE TABLE IF NOT EXISTS `kategorie` (
  2. `kategoriaId` int(11) NOT NULL AUTO_INCREMENT,
  3. `kategoriaZalezy` int(11) NOT NULL,
  4. `kategoriaNazwa` varchar(50) COLLATE utf8_polish_ci NOT NULL,
  5. `kategoriaOpis` text COLLATE utf8_polish_ci NOT NULL,
  6. `kategoriaPozycja` int(11) NOT NULL,
  7. `kategoriaAktywna` tinyint(4) NOT NULL DEFAULT '1',
  8. PRIMARY KEY (`kategoriaId`),
  9. KEY `kategoriaZalezy` (`kategoriaZalezy`)
  10. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=6 ;
  11.  
  12.  
  13.  
  14. INSERT INTO `kategorie` (`kategoriaId`, `kategoriaZalezy`, `kategoriaNazwa`, `kategoriaOpis`, `kategoriaPozycja`, `kategoriaAktywna`) VALUES
  15. (1, 0, 'cat_1', '', 2, 1),
  16. (2, 1, 'cat_1_1', '', 2, 1),
  17. (3, 0, 'cat_2', '', 1, 1),
  18. (4, 1, 'cat_1_2', '', 1, 1),
  19. (5, 4, 'cat_1_2_1', '', 1, 1);


Chciałbym Was prosić o ocenę zarówno tego kodu jaki sugestie co myślicie o obu sposobach cachowania

Pozdrawiam,
Sazian
!*!
Opisz ten kod dokładniej, w uwzględnieniem "standardu" dokumentowania kodu php. Samo zrobienie menu wielopoziomowego to nie problem. Schody zaczynają się gdy np. chcesz przenieść jakąś gałąź, u Ciebie tego nie widzę.
sazian
Cytat(!*! @ 7.05.2012, 22:13:01 ) *
Opisz ten kod dokładniej

mam nadzieje że tak wystarczy
Cytat(!*! @ 7.05.2012, 22:13:01 ) *
Samo zrobienie menu wielopoziomowego to nie problem. Schody zaczynają się gdy np. chcesz przenieść jakąś gałąź, u Ciebie tego nie widzę.

Niezgodę się z Tobą, przenoszenie jest trudne w drzewach a nie w rozwiązaniu „id | id rodzica” tu wystarczy zmienić id rodzica i ponownie przeliczyć pozycje w kategorii z której przenosimy.

Niestety pojawia się problem przy wyświetlaniu.Wyświetlanie wymaga albo wielu „join'ów” albo wczytywania rekurencyjnego w php ale to generuje wiele zapytań.
!*!
Właśnie, musisz przeliczyć. A czy to będzie wydajne tak jak masz to teraz przy załóżmy 30k pod menu, które są też rodzicami? Rzuciłem tylko okiem jak to rozwiązałeś i nie pasuje mi to, bo podchodzę do tematu nieco inaczej. Pobieram dane na podstawie id rodzica i tak rekurencyjnie gdy wystąpi dziecko, mam całą ładną tablicę którą ładuje w cache. Z przenoszeniem/kasowaniem bywa różnie... do kasowania rodzica czy dziecka używam wyrażeń regularnych są szybsze, do przenoszenia też, choć tu problemy są z przeliczeniem całej struktury i sprwdzeniem czy takowa już w wybranym drzewie nie istnieje.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.