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

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.
<?php class Menu { /** * struktura menu * @var array */ protected $dane = null; /** * Potrzebne w wersji roboczej do pobrania struktury * @var mysqli */ /** * tworzy plik cache **/ public function cache() { $query = Menu::$db->query('SELECT kategoriaId, kategoriaZalezy, kategoriaNazwa, kategoriaPozycja FROM kategorie WHERE kategoriaAktywna = 1 '); if($query->num_rows > 0) { while($row = $query->fetch_object()) { , 'parent' => $row->kategoriaZalezy//id rodzica , 'nazwa' => $row->kategoriaNazwa , 'pozycja' => $row->kategoriaPozycja//Kolejność w grupie ); } foreach($tmp as $key => $value) { foreach($dane as $key2 => $value2) { if($key != $key2) { if($value2['parent'] == $value['zalezy']) { $dane[$key2]['this_level'][] = $key; } if($value['zalezy'] == $key2) { $dane[$key2]['children'][] = $key; } } } } } } /** * pobiera plik cache, jeśli nie ma tworzy go **/ public function getCache() { { $this->cache(); } return $this->dane; } /** * Wyświetla jeden poziom „potomny” - na schemacie 'M_1_3_1' i 'M_1_3_2' * @param $id - id kategorii **/ public function drukuj_poziom($id) { if($this->dane == null) { $this->getCache(); } $ids = $this->dane[$id]['this_level']; $ids[] = $id; foreach($this->dane as $key => $value) { { $tmp[$value['pozycja']] = '<li>'.$value['nazwa'].'</li>'; } } if($r != '') { return '<ul>'. $r.'</ul>'; } else { return false; } } /** * Wyświetla całe menu dla zaznaczonej kategorii - generowane rekurencyjnie * @param $id - id aktywnej kategorii * @param $text - "kod html" kategorii wyższej - Generowane automatycznie przy wywołaniu rekurencyjnym * @param $poziom - Poziom zagnieżdżenia menu - Generowane automatycznie przy wywołaniu rekurencyjnym **/ protected function printHTML($id=0, $text = '', $poziom = 0){ if($this->dane == null) { $this->getCache(); } $poziom++; $ids = $this->dane[$id]['this_level']; $ids[] = $id; $i=0; foreach($this->dane as $key => $value) { { $i++; $tmp[$value['pozycja']] = '<li>'.$value['nazwa'].'</li>'; if($id == $key && $text != '') { $tmp[$value['pozycja']] .='<li>'.$text.'</li>'; } { $tmp[$value['pozycja']].='<li>'.$this->drukuj_poziom($this->dane[$id]['children'][0]).'</li>'; } } } if($r != '' ) { $r = '<ul>'. $r.'</ul>'; } if($this->dane[$id]['parent'] > 0) { $r = $this->printHTML($this->dane[$id]['parent'], $r, $poziom); } return $r; } /** * Wyświetla całe menu dla zaznaczonej kategorii - nakładka na printHTML * @param $id - id aktywnej kategorii **/ public function treeHTML($id=0) { return $this->printHTML($id); } } $db_host = ''; $db_name = ''; $db_password = ''; $db_user = ''; $db = new mysqli($db_host, $db_user, $db_password, $db_name); $db->query('SET NAMES utf8'); $db->query('SET CHARACTER SET utf8_general_ci'); Menu::$db=$db; $menu = new Menu(); ?>
przykładowa baza
CREATE TABLE IF NOT EXISTS `kategorie` ( `kategoriaId` int(11) NOT NULL AUTO_INCREMENT, `kategoriaZalezy` int(11) NOT NULL, `kategoriaNazwa` varchar(50) COLLATE utf8_polish_ci NOT NULL, `kategoriaOpis` text COLLATE utf8_polish_ci NOT NULL, `kategoriaPozycja` int(11) NOT NULL, `kategoriaAktywna` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`kategoriaId`), KEY `kategoriaZalezy` (`kategoriaZalezy`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=6 ; INSERT INTO `kategorie` (`kategoriaId`, `kategoriaZalezy`, `kategoriaNazwa`, `kategoriaOpis`, `kategoriaPozycja`, `kategoriaAktywna`) VALUES (1, 0, 'cat_1', '', 2, 1), (2, 1, 'cat_1_1', '', 2, 1), (3, 0, 'cat_2', '', 1, 1), (4, 1, 'cat_1_2', '', 1, 1), (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