Menu klasse
Ik wil een menu klasse gaan schrijven, de bedoeling is dat het menu uit de database komt. Er kunnen ook sub-menu's zijn.
Nu zat ik zelf te denken aan een menu klasse.
klasse Menu:
-setName($name)
-setUrl($url)
-setParentId($parentid)
-setId()
-getName() (array)
-getUrl() (array)
-getParentId (array)
-getId() (array)
klasse MenuMapper:
-getMenu() (haalt het menu uit de database en maakt de klasse menu aan)
Ik vroeg me af of dit een goede opzet is, en hoe ik verder alle gets bij elkaar kan voegen aangezien alles los word opgeslagen van 1 menu item?
Menu_list
# items = array()
# rol = ''
+ constructor( array $items = array(), $rol = 'primary' )
+ addItem( $item )
+ removeItem( $id )
Menu_item
# url
# name
+ constructor( $name, $url )
+ setName( $name )
+ setUrl( $url )
+ getName( $name )
+ getUrl( $url )
+ __toString()
Dan kun je ook veel makkelijker submenu's maken:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$menu = new Menu_list();
// normale menu items
$menu->addItem( new Menu_item('Home', '/home') );
$menu->addItem( new Menu_item('About', '/about') );
// submenu's
$menu->addItem( new Menu_list(array(
new Menu_item('Foo', '/category/1/foo'),
new Menu_item('Bar', '/category/2/bar'),
new Menu_item('Lorem', '/category/3/lorem'),
)) );
?>
$menu = new Menu_list();
// normale menu items
$menu->addItem( new Menu_item('Home', '/home') );
$menu->addItem( new Menu_item('About', '/about') );
// submenu's
$menu->addItem( new Menu_list(array(
new Menu_item('Foo', '/category/1/foo'),
new Menu_item('Bar', '/category/2/bar'),
new Menu_item('Lorem', '/category/3/lorem'),
)) );
?>
Hoe wil jij het op jou manier uit de database halen? Want aan jou voorbeeld kan ik niet zien bij welk menu de sub-items horen?
Verder geef jij in je voorbeeld $name en $url mee aan de __contruct, waarom heb je dan nog een setName() en setUrl() methode?
Quote:
dit is opzicht wel duidelijk maar je kunt natuurlijk ook alles in een array gooien? En daarna de array verdelen/sorteren?
Ja, maar dan moet je alsnog werken met een menu item klasse. Want je hebt nu gewoon properties die niet bij het menu object horen. Als id, parent_id, url, name. Dat zijn allemaal eigenschappen van een menu item en niet van een menu.
Dus als je alles in een array stopt heb je minder OO en wordt de klasse meer een verzameling functies (wat ook niet slecht is, maar dan is het geen OO meer).
Quote:
Hoe wil jij het op jou manier uit de database halen? Want aan jou voorbeeld kan ik niet zien bij welk menu de sub-items horen?
Dat is ook een probleem waaraan ik zat te denken. Je zou een Menu_item Mapper moeten maken, maar misschien ook een Menu_list Mapper.
Quote:
Verder geef jij in je voorbeeld $name en $url mee aan de __contruct, waarom heb je dan nog een setName() en setUrl() methode?
De naam en de url zijn eigenschappen die je nodig hebt voor het correct werken van een klasse. Vandaar dat ze aan de constructor meegegeven worden.
Maar omdat in OO alles draait om flexibiliteit zitten er ook nog aparte setters in, zodat je halverwege het script de url/name kunt wijzigen.
Wat zou je verder met een menu klasse van kunnen doen?
Verder weet ik ook nog niet goed hoe ik dit in OO moet oplossen met de sub items. Misschien dat het helpt om de manier te delen hoe ik het zonder OO deed. Misschien dat jullie dan weten hoe ik het in OO op de beste manier kan doen.
Code (php)
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<?php
public function getMenu($i_parentid)
{
$string = null;
$result = $this->_db->prepare("SELECT
webn_name,
webn_url,
webn_parentid,
webn_id
FROM
web_navigation
WHERE
webn_state = 1
AND
webn_parentid = ?
ORDER BY
webn_order
ASC
");
$result->execute(array($i_parentid));
$list = $result->fetchAll();
foreach($list as $array)
{
$string[] = $array;
}
return $string;
}
$getMenu = $index->getMenu(0);
if(count($getMenu) > 0)
{
foreach($getMenu as $menu)
{
echo '<li class="menu"' . ($menu['webn_url'] == $config->getUrl(1) ? ' class="select"' : '') . '>';
echo ($menu['webn_url'] != '' ? '<a href="' . SITE_PATH . $menu['webn_url'] . '/">' : '');
echo $config->replaceMessage($menu['webn_name']);
echo ($menu['webn_url'] != '' ? '</a>' : '');
$getSubMenu = $index->getMenu($menu['webn_id']);
if(count($getSubMenu) > 0)
{
echo '<ul class="menusub">';
foreach($getSubMenu as $sub)
{
echo '<li class="menusub"' . ($sub['webn_url'] == $config->getUrl(1) ? ' class="select"' : '') . '>';
echo ($sub['webn_url'] != '' ? '<a href="' . SITE_PATH . $sub['webn_url'] . '/">' : '');
echo $config->replaceMessage($sub['webn_name']);
echo ($sub['webn_url'] != '' ? '</a>' : '');
echo '</li>';
}
echo '</ul>';
}
echo '</li>';
}
}
else
{
echo 'ERROR: No menu items';
}
?>
public function getMenu($i_parentid)
{
$string = null;
$result = $this->_db->prepare("SELECT
webn_name,
webn_url,
webn_parentid,
webn_id
FROM
web_navigation
WHERE
webn_state = 1
AND
webn_parentid = ?
ORDER BY
webn_order
ASC
");
$result->execute(array($i_parentid));
$list = $result->fetchAll();
foreach($list as $array)
{
$string[] = $array;
}
return $string;
}
$getMenu = $index->getMenu(0);
if(count($getMenu) > 0)
{
foreach($getMenu as $menu)
{
echo '<li class="menu"' . ($menu['webn_url'] == $config->getUrl(1) ? ' class="select"' : '') . '>';
echo ($menu['webn_url'] != '' ? '<a href="' . SITE_PATH . $menu['webn_url'] . '/">' : '');
echo $config->replaceMessage($menu['webn_name']);
echo ($menu['webn_url'] != '' ? '</a>' : '');
$getSubMenu = $index->getMenu($menu['webn_id']);
if(count($getSubMenu) > 0)
{
echo '<ul class="menusub">';
foreach($getSubMenu as $sub)
{
echo '<li class="menusub"' . ($sub['webn_url'] == $config->getUrl(1) ? ' class="select"' : '') . '>';
echo ($sub['webn_url'] != '' ? '<a href="' . SITE_PATH . $sub['webn_url'] . '/">' : '');
echo $config->replaceMessage($sub['webn_name']);
echo ($sub['webn_url'] != '' ? '</a>' : '');
echo '</li>';
}
echo '</ul>';
}
echo '</li>';
}
}
else
{
echo 'ERROR: No menu items';
}
?>
Toevoeging op 05/03/2012 17:54:51:
Als ik het zo zelf nog eens bekijk denk ik nu aan de volgende klasse:
-MenuListMapper
-getMenu($parentId)
-MenuList
-setName($name)
-setUrl($url)
-setParentId($parentId)
-setId($id)
-getName()
-getUrl
-getParentId()
-getId()
Code (php)
1
2
3
4
2
3
4
$menuListMapper = new MenuListMapper($db);
$menuList = $menuListMapper->getMenu(0);
$menuListSub = $menuListMapper->getMenu('PARENT_ID');
$menuList = $menuListMapper->getMenu(0);
$menuListSub = $menuListMapper->getMenu('PARENT_ID');
Of zou ik twee methode's moeten maken? Menu() MenuSub()?
Tocht ben ik er nog niet helemaal over uit. Hoop dat jullie mij beter kunnen helpen!
Het ophalen van het menu zou ik doen met een MenuRepository waar je gewoon direct het hele menu of het menu vanaf een bepaald subitem op kan vragen. Het liefst heb je niet dat de Menu en MenuItem klassen weten dat ze opgeslagen kunnen worden en hoe dit gebeurt. Dat maakt het later ook eenvoudiger om een XML opslag techniek te gebruiken bv.
Quote:
Maar omdat in OO alles draait om flexibiliteit zitten er ook nog aparte setters in, zodat je halverwege het script de url/name kunt wijzigen.
Zeer slecht argument. Een setter wel of niet definiëren heeft niets met flexibiliteit te maken. Sterker nog ik zou een setter alleen definiëren als ik hier een heel goede reden voor had. Het druist namelijk in tegen het OOP principe van het afschermen van data. Je doet dat op zo'n moment namelijk nog maar gedeeltelijk.
De meeste transities van state van een object zal je binnen het eigen object willen laten plaatsvinden. In een eigen methode. Op het moment dat je een setter maakt, geef je deze "macht" af aan andere klassen. Iedereen kan opeens deze state veranderen. Natuurlijk kan de setter wel logica bevatten, maar dit doet deze eigenlijk nooit.
Indien je bereid bent wat extra moeite te doen, kan je de structuur ook anders regelen.
MySQL heeft een systeem waarmee je gegevens kan ordenen, in een hiërarchische structuur.
Je kan dan oneindig complex (nu ja, ...) items in een hiërarchie steken, zonder op voorhand te moeten weten hoe diep de structuur moet gaan.
In jouw geval kan je dus een submenu hebben die zelf een aantal items hebben die submenu's hebben.
Tja, zoals dat dus het geval is bij menu's van programma's op je pc.
Hier vind je uitleg:
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
Dus nog eens duidelijk maken: hiervoor zal je wat tijd moeten maken om het onder de knie te krijgen en waarschijnlijk is het overkill voor wat je nu nodig hebt.
Indien je het de moeite niet vind, ben ik de eerste om dit goed te begrijpen, doe dan maar voort zoals je bezig bent.
@Kees: Bedankt, maar op die manier genereer je de HTML en niet de menu items zelf.
@Kris: Is het verstandig om dat met MySQL te doen? Ik ben dat ook nog nooit eerder tegen gekomen dus vraag mezelf af of het wel verstandig is om dat toe te passen.
--
Verder heb ik nog niet veel duidelijkheid over hoe ik de opbouw zou kunnen doen in OO. Hoe weet ik straks welk sub-item bij welk item hoort? Is het idee van de getters en setters wel goed?
Allereerst, wat heeft een menu met de user te maken? Dus waarom zou je dat opeens uit een UserMapper halen?
Dan de structuur die wij voorstellen zijn twee klassen:
- Menu
- MenuItem
Een menu is het hele menu. Het MenuItem is een los item uit het menu.
Voorbeeldje:
Het hoofdmenu is nu een Menu, dus Menu object. De items Home, Inloggen, Form en Blog zijn nu MenuItems.
De klasse Menu en MenuItem hoeven niet te weten dat een menu uit de database komt daar zorgt de MenuMapper() voor.
Verder maakt de MenuMapper() het Menu() en MenuItem() object aan wanneer het menu uit de database word gehaald?
Begrijp ik het nu een beetje?
Jij gaat uit van 1 top-menu, elk top-menu kan 1 generatie submenu's bevatten.
In dit geval kan je doen wat jij doet: het volstaat dat een submenu zijn parent (id) bijhoudt.
Trouwens, al eens nagedacht over de volgorde van de items van een submenu? Stel dat je (sub)item 5 eigenlijk liever op de tweede plaats hebt; hoe maak je dat duidelijk in de database?
Het systeem dat ik voorstel werkt niet met een parent-veld. Elk item heeft een left-waarde en een right-waarde (steeds een integer).
Elke left-waarde of right-waarde komt slechts 1 keer voor.
Met die waarden heeft elk item ondubbelzinnig zijn plaats in de structuur.
Dat laat gelijk welke boomstructuur toe. Zie vooral:
Waar dit artikel over gaat, is dus een reeks sql statements die heel dit systeem regelen.
bv. indien right - left = 1 => dan weet je dat het een "leaf node" is: een blad, een eindpunt (geen subitems).
Je hebt statemants om - een hele tak te verhuizen naar een andere node, items van plaats te wisselen, ...
Het zit goed in mekaar; het werkt; het laat jou veel mogelijkheden toe.
... maar dan wel meer dan je vraagt; je moet zelf bepalen of het de moeite waard is.
Gewijzigd op 07/03/2012 15:11:11 door Kris Peeters
Ik vind het idee van je wel erg interessant, enkel snap ik het nog niet helemaal "indien right - left = 1" geen sub-items.
Wanneer heeft het dan wel sub-items?
En hoe weet ik nu dat TELEVISIONS bijvoorbeeld een sub-item is?
Left: 10; right: 19.
Er zijn 8 getallen tussen 10 en 19, dus je weet dat er in totaal 4 nodes staan onder "Portable electronics".
Stel dat je de hele tak onder "Portable electronics" wil verwijderen. Dan moet je dus alles verwijderen met een left en right tussen de waarden 10 en 19.
Daarna moet het gat terug gevuld worden: alle getallen groter dan 10 moeten met 8 verminderen.
"Portable electronics" wordt dan: left: 10; right: 11.
Electronics wordt dan: left: 1; right 12
Zo is er dus een procedure voor alle mogelijke dingen die je met een boomstructuur kan uitsteken. En je moet die zelf niet uitvinden: die staan in het artikel
Gewijzigd op 07/03/2012 15:39:32 door Kris Peeters
Enkel ben ik het nog niet helemaal eens over de manier, wanneer je een nieuw sub-item krijgt moeten alle nummertjes aangepast worden?
Gebruik je deze manier zelf ook voor een menu op te halen?
Dit lijkt een vreemde manier van doen, maar het werkt.
Google eens "hierarchical mysql". Alle zoekresultaten vertellen het zelfde verhaal.
Dit artikel stond nog op de site van mysql zelf.
(Ik bedoel maar ... don't take my word for it, zie eens wat de rest van de wereld er van vindt; ik heb dit niet bedacht.)
De echte vraag is: is het de moeite waard voor wat je maar nodig hebt?
Dit soort dingen kan je vaak best eerst apart testen. Los van waar je echt mee bezig bent.
Het is al een aantal jaar geleden dat ik dit echt nog gebruikte. Toen was de noodzaak ook groter; de structuur was complexer dan de vraag die jij stelt.
Toevoeging op 07/03/2012 17:54:08:
Ik heb een mooi artikel gevonden op Pfz.nl -> http://www.pfz.nl/wiki/hierarchie-in-een-database/ ik ga daar eens even naar kijken!
Toevoeging op 07/03/2012 19:02:40:
Ik heb volgende artikel: http://netters.nl/artikelen/php-en-mysql/hierarchie-in-de-database/ lees ik dat "Het modified preorder tree traversel model" de beste oplossing is, omdat dit de snelste manier is.
Echter kan je ook gewoon een view maken, en hoef hij die view alleen aan te passen wanneer er iets gewijzigd word.
Toch vind ik die andere manier wel makkelijk, alleen kost het extra rekentijd bij het aanmaken en verwijderen van een item.
Toevoeging op 07/03/2012 21:04:56:
Ik ben het nu voor elkaar echter gaat er nog iets mis, de resultaten kloppen niet er worden bepaalde dingen niet afgesloten?
Ook klopt het resultaat niet aangezien cat01, cat02 en cat03 hoofd items zijn.
Code:
Code (php)
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
30
31
32
33
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
30
31
32
33
$resultUser = $db->prepare("SELECT node.name, COUNT(*) - 1 AS lvl
FROM menu AS node,
menu AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.lft, node.name
ORDER BY node.lft");
$resultUser->execute(array());
$listUser = $resultUser->fetchAll();
if(count($listUser) > 0)
{
echo '<ul>';
foreach($listUser as $i => $user)
{
echo '<li>
'. $user['name'];
if($listUser[$i+1]['lvl'] > $listUser[$i]['lvl'])
{
echo '<ul>';
}
elseif($listUser[$i+1]['lvl'] < $listUser[$i]['lvl'])
{
echo '</li>
</ul>
</li>';
}
else
{
echo '</li>';
}
}
echo '</ul>';
}
FROM menu AS node,
menu AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.lft, node.name
ORDER BY node.lft");
$resultUser->execute(array());
$listUser = $resultUser->fetchAll();
if(count($listUser) > 0)
{
echo '<ul>';
foreach($listUser as $i => $user)
{
echo '<li>
'. $user['name'];
if($listUser[$i+1]['lvl'] > $listUser[$i]['lvl'])
{
echo '<ul>';
}
elseif($listUser[$i+1]['lvl'] < $listUser[$i]['lvl'])
{
echo '</li>
</ul>
</li>';
}
else
{
echo '</li>';
}
}
echo '</ul>';
}
Output:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<ul>
<li>cat01
<ul>
<li>cat01A
<ul>
<li>cat01B
<ul>
<li>cat01C1</li>
<li>cat01C2</li>
</ul>
</li>
<li>cat02</li>
<li>cat03
<ul>
<li>cat03A</li>
</ul>
</li>
</ul>
<li>cat01
<ul>
<li>cat01A
<ul>
<li>cat01B
<ul>
<li>cat01C1</li>
<li>cat01C2</li>
</ul>
</li>
<li>cat02</li>
<li>cat03
<ul>
<li>cat03A</li>
</ul>
</li>
</ul>
SQL
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE IF NOT EXISTS `menu` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
`lft` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=9 ;
--
-- Gegevens worden uitgevoerd voor tabel `menu`
--
INSERT INTO `menu` (`id`, `name`, `lft`, `rgt`) VALUES
(1, 'cat01', 1, 10),
(2, 'cat01A', 2, 9),
(3, 'cat01B', 3, 8),
(4, 'cat01C1', 4, 5),
(5, 'cat01C2', 6, 7),
(6, 'cat02', 11, 12),
(7, 'cat03', 13, 16),
(8, 'cat03A', 14, 15);
`id` int(11) NOT NULL auto_increment,
`name` varchar(20) NOT NULL,
`lft` int(11) NOT NULL,
`rgt` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=9 ;
--
-- Gegevens worden uitgevoerd voor tabel `menu`
--
INSERT INTO `menu` (`id`, `name`, `lft`, `rgt`) VALUES
(1, 'cat01', 1, 10),
(2, 'cat01A', 2, 9),
(3, 'cat01B', 3, 8),
(4, 'cat01C1', 4, 5),
(5, 'cat01C2', 6, 7),
(6, 'cat02', 11, 12),
(7, 'cat03', 13, 16),
(8, 'cat03A', 14, 15);
Gewijzigd op 08/03/2012 02:04:30 door Tom Swinkels
Eventueel een dummy node (noem die bv. root).
Een aantal bewerkingen worden veel lastiger als je geen root hebt (bv. een tak verplaatsen onder een andere node)
Zie dat je zeker een veld depth in je records hebt (jij noemt die lvl, als ik het goed heb).
depth = 0 => (dummy) root
depth = 1 => menu
depth = 2 => submenu
Je werkt met pdo?
Dat is al interessant. Het zal nodig zijn tables te locken en pas te commiten op commando.
Er kunnen best een aantal functies gemaakt worden om de boel wat meer overzichtelijk te maken. Het zijn lange queries.
iets als
insert_as_child($db_object, $parent_id) // inserten onder een node, als eerste subitem (eventueel al de rest opschuiven naar rechts)
insert_as_sibling($db_object, $sibling_id) // inserten als broer/zus rechts van een node
Dat wordt wel een interesante oefening om dit goed te structureren.
Dat openen en sluiten van ul's en li's ... dat is wat zoeken. Nu onmiddelijk even geen tijd, sorry
Tom Swinkels op 07/03/2012 17:39:13:
Toch vind ik die andere manier wel makkelijk, alleen kost het extra rekentijd bij het aanmaken en verwijderen van een item.
Dat is juist.
Maar je moet dat wel zien vanuit de verhouding van het aantal keer dat je de data schrijft/update ten opzichte van hoe veel je data leest.
Je zal ook liever een thumbnail maken op het moment van de upload; dan telkens de GD library aan te spreken bij het bekijken van de foto galerij
Gewijzigd op 08/03/2012 11:36:34 door Kris Peeters
En hij alleen tot een x aantal sub-menu's goed werkt.
Nu heb ik even een schema gemaakt waar ik even vanuit ga:
Als de left en right van de menu items tussen de 1 en 12 hoort het allemaal onder menu 01.
Wanneer de left en right van de menu items tussen de 2 en 9 zitten horen de items onder het sub-menu 01-1A.
Ik weet echter niet hoe ik dit goed kan uitrekenen, hopelijk kan iemand mij hierbij helpen.
Nested Set model. Een zeer bekend model om hiërarchische structuren op te slaan in een database.
De techniek werkt erg leuk voor als je de volledige boom of een deel daarvan in één keer wil opvragen. Dat kan namelijk eenvoudig met een enkele query die gebruik maakt van de left en de right waarden. Er zijn alleen ook een paar nadelen:
- Het is heel lastig om inserts te doen. Je zal dan heel de boom opnieuw moeten uitrekenen en alle left en right waarden aan moeten passen. Je kan dit wel afvangen door grotere marges te nemen, maar vroeg of laat krijg je hier toch een probleem mee
- Je hebt geen mogelijkheid om de consistentie van je structuur af te dwingen doormiddel van constraints. Hierdoor kunnen er eenvoudig fouten ontstaan doordat je left en right waarden tussen twee menu items overlappen
- Het ophalen van een enkel niveau in je boom is erg lastig
Een andere manier om het op te slaan die in principe ook veel logischer is door een link te maken van een menu_item naar een ander menu_item waar deze onder moet vallen. Je geeft je menu item dus een parent_id. Dit parent_id is null als er geen parent is.
Deze techniek heeft een aantal voordelen:
- Het is eenvoudiger om nieuwe menu_items toe te voegen. Dit kan gewoon door een nieuw record toe te voegen. Je hoeft de andere records niet aan te passen
- Je kan eenvoudig een enkele laag uit je boomstructuur opvragen
- Je kan de consistentie van je boom afdwingen doormiddel van foreign keys
Natuurlijk heeft deze techniek ook nadelen:
- Je hebt recursie nodig om alle records uit een boom op te vragen. Dit kan je in PHP doen door voor elke laag in de boom een query uit te voeren of doormiddel van common table expressions.
Overigens heeft de hele discussie over hoe je dit op gaat slaan in de database zeer weinig te maken met je object model in je applicatie. Dat is namelijk de grap van OOP en met name het Domain model pattern. Je domein model beschrijft de entiteiten en de business rules, maar weet niets af van persistence. Dat is iets dat je vervolgens in een architecture layer neerzet en laat beheren door een ORM of iets dergelijks.
Het principe dat hierboven wordt beschreven is het De techniek werkt erg leuk voor als je de volledige boom of een deel daarvan in één keer wil opvragen. Dat kan namelijk eenvoudig met een enkele query die gebruik maakt van de left en de right waarden. Er zijn alleen ook een paar nadelen:
- Het is heel lastig om inserts te doen. Je zal dan heel de boom opnieuw moeten uitrekenen en alle left en right waarden aan moeten passen. Je kan dit wel afvangen door grotere marges te nemen, maar vroeg of laat krijg je hier toch een probleem mee
- Je hebt geen mogelijkheid om de consistentie van je structuur af te dwingen doormiddel van constraints. Hierdoor kunnen er eenvoudig fouten ontstaan doordat je left en right waarden tussen twee menu items overlappen
- Het ophalen van een enkel niveau in je boom is erg lastig
Een andere manier om het op te slaan die in principe ook veel logischer is door een link te maken van een menu_item naar een ander menu_item waar deze onder moet vallen. Je geeft je menu item dus een parent_id. Dit parent_id is null als er geen parent is.
Deze techniek heeft een aantal voordelen:
- Het is eenvoudiger om nieuwe menu_items toe te voegen. Dit kan gewoon door een nieuw record toe te voegen. Je hoeft de andere records niet aan te passen
- Je kan eenvoudig een enkele laag uit je boomstructuur opvragen
- Je kan de consistentie van je boom afdwingen doormiddel van foreign keys
Natuurlijk heeft deze techniek ook nadelen:
- Je hebt recursie nodig om alle records uit een boom op te vragen. Dit kan je in PHP doen door voor elke laag in de boom een query uit te voeren of doormiddel van common table expressions.
Overigens heeft de hele discussie over hoe je dit op gaat slaan in de database zeer weinig te maken met je object model in je applicatie. Dat is namelijk de grap van OOP en met name het Domain model pattern. Je domein model beschrijft de entiteiten en de business rules, maar weet niets af van persistence. Dat is iets dat je vervolgens in een architecture layer neerzet en laat beheren door een ORM of iets dergelijks.
Ik ben toch afgestapt van het Nested Set model, er zitten toch teveel nadelen aan.
Nu wil ik een recursive manier gebruiken, ik heb hier een stuk op internet gevonden en dat werkt. Nu wil ik die manier ombouwen tot OO in klasses.
De HTML wil ik niet in de klasse, het moet wel makkelijk zijn om hem bijvoorbeeld om te bouwen naar een template.
Ik zou dit splitsen tot 3 klasses (Menu, MenuItem en MenuMapper). Enkel kan ik de opbouw nog niet helemaal thuis brengen.
Misschien dat jullie mij nog wat tip's kunnen geven?
Mijn opbouw tot nu toe
Code (php)
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
class Menu
{
function getMenu()
{
}
}
class MenuItem
{
private $_data;
private $_list;
private $_id;
private $_parentId;
public function __construct()
{
$this->_data = array();
}
public function setList($list)
{
$this->_list = $list;
}
public function setId($id)
{
$this->_id = $id;
}
public function setParentId($parentId)
{
$this->_parentId = $parentId;
}
public function getIds()
{
return $this->_data['ids'][$this->_id] = $this->_list;
}
public function getParents()
{
return $this->_data['parents'][$this->_parentId][] = $this->_id;
}
}
class MenuMapper
{
public function __construct(PDO $db)
{
$this->_db = $db;
}
public function getMenu()
{
$resultWebn = $this->_db->prepare("SELECT
webn_id,
webn_parentid,
webn_name
FROM
web_navigation
ORDER BY
webn_parentid, webn_name
");
$resultWebn->execute(array());
$listWebn = $resultWebn->fetchAll();
foreach($listWebn as $list)
{
$menuItem = new MenuItem();
$menuItem->setList($list);
$menuItem->setId($list['webn_id']);
$menuItem->setParentId($list['webn_parentid']);
return $menuItem;
}
}
}
?>
class Menu
{
function getMenu()
{
}
}
class MenuItem
{
private $_data;
private $_list;
private $_id;
private $_parentId;
public function __construct()
{
$this->_data = array();
}
public function setList($list)
{
$this->_list = $list;
}
public function setId($id)
{
$this->_id = $id;
}
public function setParentId($parentId)
{
$this->_parentId = $parentId;
}
public function getIds()
{
return $this->_data['ids'][$this->_id] = $this->_list;
}
public function getParents()
{
return $this->_data['parents'][$this->_parentId][] = $this->_id;
}
}
class MenuMapper
{
public function __construct(PDO $db)
{
$this->_db = $db;
}
public function getMenu()
{
$resultWebn = $this->_db->prepare("SELECT
webn_id,
webn_parentid,
webn_name
FROM
web_navigation
ORDER BY
webn_parentid, webn_name
");
$resultWebn->execute(array());
$listWebn = $resultWebn->fetchAll();
foreach($listWebn as $list)
{
$menuItem = new MenuItem();
$menuItem->setList($list);
$menuItem->setId($list['webn_id']);
$menuItem->setParentId($list['webn_parentid']);
return $menuItem;
}
}
}
?>
Download
Code (php)
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
30
31
32
33
34
35
36
37
38
39
40
41
42
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
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
$result = $db->prepare("SELECT
webn_id, webn_parentid, webn_name
FROM
web_navigation
ORDER BY
webn_parentid, webn_name
");
$result->execute(array());
$listWebn = $result->fetchAll();
$menuData = array(
'items' => array(),
'parents' => array()
);
foreach($listWebn as $list)
{
$menuData['items'][$list['webn_id']] = $list;
$menuData['parents'][$list['webn_parentid']][] = $list['webn_id'];
}
function buildMenu($parentId, $menuData)
{
$html = null;
if (isset($menuData['parents'][$parentId]))
{
$html = '<ul>';
foreach ($menuData['parents'][$parentId] as $itemId)
{
$html .= '<li><a href="">' . $menuData['items'][$itemId]['webn_name'] . '</a>';
$html .= buildMenu($itemId, $menuData);
$html .= '</li>';
}
$html .= '</ul>';
}
return $html;
}
?>
$result = $db->prepare("SELECT
webn_id, webn_parentid, webn_name
FROM
web_navigation
ORDER BY
webn_parentid, webn_name
");
$result->execute(array());
$listWebn = $result->fetchAll();
$menuData = array(
'items' => array(),
'parents' => array()
);
foreach($listWebn as $list)
{
$menuData['items'][$list['webn_id']] = $list;
$menuData['parents'][$list['webn_parentid']][] = $list['webn_id'];
}
function buildMenu($parentId, $menuData)
{
$html = null;
if (isset($menuData['parents'][$parentId]))
{
$html = '<ul>';
foreach ($menuData['parents'][$parentId] as $itemId)
{
$html .= '<li><a href="">' . $menuData['items'][$itemId]['webn_name'] . '</a>';
$html .= buildMenu($itemId, $menuData);
$html .= '</li>';
}
$html .= '</ul>';
}
return $html;
}
?>
Gewijzigd op 11/03/2012 23:08:12 door Tom Swinkels