MVC pagina
"Vroeger" dan laadde ik op basis van een route een pagina-controller. Zo heb ik het ooit geleerd met Zend Framework (1). Bijv. mijnsite.nl/contact, dan was "contact" de route en werd de indexAction() van de contactController class aangeroepen. Iedere pagina had dus een eigen controller.
Nu wil ik het anders gaan doen. Ik wil niet dat 1 pagina overeenkomt met 1 controller. In plaats daarvan wil ik een pagina waarop ik verschillende modules kan plaatsen die ieder op zich weer een eigen controller hebben. Wat ik me alleen even afvraag is hoe dat proces (in grote lijnen!) er uitziet.
Ik dacht dus zelf om een route (bijv. "contact") te koppelen aan een pagina-configuratiebestand (/page/contact/configuration) en dat ik dan in dat bestand aangeef wat er op die betreffende pagina moet komen te staan. Zoiets als:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
module: header
module: title
title: Contactgegevens
module: contact
show_address: true
show_map : false
module: footer
module: title
title: Contactgegevens
module: contact
show_address: true
show_map : false
module: footer
Is het een goed/acceptabel idee om het op deze manier aan te pakken? Het gaat niet om de details, maar met name om de globale opzet. Dus een route die verwijst naar een config-bestand waarin de modules worden ingesteld.
Voor elke pagina een apart configuratiebestand? Met daarin content zoals "title: Contactgegevens"? Dat lijkt geen goed idee.
De basisgedachte is wel goed: niet één controller per pagina, maar meerdere voor verschillende onderdelen die in verschillende webpagina's worden hergebruikt. HMVC daarom.
En wat is volgens jou een "module"? Is dat simpelweg een View die je dan inlaadt? Waarbij je dan in je configuratie ook nog aangeeft welke 'stukken' je in die view wel en niet wilt laten zien?
Dat klinkt voor mij echt als View logica. Niet iets wat je zo in een configuratie bestand zou moeten maken.
Een configuratiebestand, of misschien wel gewoon een eigen "page" class per pagina? En nee, je zet er dus niet alleen de titel in, maar ook de modules + de configuratie daarvan die op die pagina moeten komen te staan. Zie het code-voorbeeld in mijn eerste post.
>> De basisgedachte is wel goed: niet één controller per pagina, maar meerdere voor verschillende onderdelen die in verschillende webpagina's worden hergebruikt. HMVC daarom.
En hoe ziet zo'n bestand voor 1 pagina er dan uit? Heb je daar toevallig een voorbeeld van?
>> Het hoeft helemaal niet zo te zijn dat 1 pagina overeen komt met 1 controller. Je kan toch meerdere Action methods maken in een controller. Kan me voorstellen dan een "Contact" controller maar 1 pagina heeft. Maar een UserController zou er bijvoorbeeld meer kunnen hebben. Overview, Edit etc.
Dit is toch precies wat ik zeg?
>> En wat is volgens jou een "module"? Is dat simpelweg een View die je dan inlaadt? Waarbij je dan in je configuratie ook nog aangeeft welke 'stukken' je in die view wel en niet wilt laten zien?
Met een module bedoel ik een "onderdeel" met een eigen controller, model en view. Stel je hebt één pagina met daarop een header, nieuwsberichten en een footer, dan zijn dit 3 modules.
Snap je wat ik bedoel?
Ik wil dus (schematisch) zoiets kunnen doen:
Pagina "huppeldepup":
Gewijzigd op 20/02/2014 16:26:13 door Ozzie PHP
Ozzie PHP op 20/02/2014 16:25:29:
Waarom dan niet een database inzetten? Je gaat nu per webpagina een variabele zoals de paginatitel in een YAML-configuratie zetten...>> Voor elke pagina een apart configuratiebestand? Met daarin content zoals "title: Contactgegevens"? Dat lijkt geen goed idee.
Een configuratiebestand, of misschien wel gewoon een eigen "page" class per pagina? En nee, je zet er dus niet alleen de titel in, maar ook de modules + de configuratie daarvan die op die pagina moeten komen te staan. Zie het code-voorbeeld in mijn eerste post.
Een configuratiebestand, of misschien wel gewoon een eigen "page" class per pagina? En nee, je zet er dus niet alleen de titel in, maar ook de modules + de configuratie daarvan die op die pagina moeten komen te staan. Zie het code-voorbeeld in mijn eerste post.
Ward, het is ook nog maar een idee. De settings van één pagina opslaan in een bestand lijkt me wel handig. Hoe doet dat HMVC dat dan?
HMVC is een bruikbaar model voor webpagina's omdat die een duidelijke hiërarchische structuur hebben. Neem bijvoorbeeld een adresvermelding in de footer:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
<html>
...
<body>
...
<footer>
...
<address>
...
</address>
</footer>
</body>
</html>
...
<body>
...
<footer>
...
<address>
...
</address>
</footer>
</body>
</html>
In een HMVC-opzet kun je dan zeggen dat de footer-module in de hiërarchie boven de address-module staat:
HMVC-module Footer > HMVC-module Address
Die parent MVC, is dat dan een soort van "page" module, waarmee je de titel en metatags van een pagina aangeeft? Moet ik dat op die manier zien? En wat voor actions zou een "page" module kunnen hebben? Alleen een indexAction()? En is het dan de bedoeling dat de page module de GET en POST data doorgeeft aan de child modules?
Ik zit maar een beetje hardop te brainstormen nu hè... alle advies is welkom.
Scaling Web Applications with HMVC:
HMVC-modules hoeven niet altijd een hiërarchie te hebben: ze kunnen ook "naast" elkaar staan. Denk bijvoorbeeld aan een top-banner en een bottom-banner. Die zijn gelijkwaardig, maar kunnen toch van elkaar afhankelijk zijn wanneer je bannermanagement een regel afdwingt zoals "in de bottom-banner moet altijd een andere advertentie worden getoond dan in de top-banner". Ze hebben dan geen parent/child-relatie, maar het zijn eerder siblings die beide afhankelijk zijn van dezelfde parent-controller.
Ja, helemaal correct. Plaatje van MVC naast HMVC uit HMVC-modules hoeven niet altijd een hiërarchie te hebben: ze kunnen ook "naast" elkaar staan. Denk bijvoorbeeld aan een top-banner en een bottom-banner. Die zijn gelijkwaardig, maar kunnen toch van elkaar afhankelijk zijn wanneer je bannermanagement een regel afdwingt zoals "in de bottom-banner moet altijd een andere advertentie worden getoond dan in de top-banner". Ze hebben dan geen parent/child-relatie, maar het zijn eerder siblings die beide afhankelijk zijn van dezelfde parent-controller.
Kruig je dan zoiets als dit? Of werkt het anders?
Code (php)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<?php
public function indexAction() {
$modules = $this->modules;
$top_banner = $modules->get('banner');
$bottom_banner = $modules->get('banner');
$top_banner->setAction('show');
$bottom_banner->setAction('show');
}
?>
public function indexAction() {
$modules = $this->modules;
$top_banner = $modules->get('banner');
$bottom_banner = $modules->get('banner');
$top_banner->setAction('show');
$bottom_banner->setAction('show');
}
?>
En hoe zorg je er dan voor dat ze allebei iets anders tonen?
Bijvoorbeeld door één parent-controller verantwoordelijk te maken voor de twee child-controllers. Schematisch:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
<?php
$banners = new BannerManagementController();
$banners->setNumberOfBanners(2);
$top_banner = $banners->getBannerById(0);
$bottom_banner = $banners->getBannerById(1);
?>
$banners = new BannerManagementController();
$banners->setNumberOfBanners(2);
$top_banner = $banners->getBannerById(0);
$bottom_banner = $banners->getBannerById(1);
?>
Abstract gezegd: de twee banners zijn siblings die niet van elkaars bestaan weten. Alleen de controller erboven weet dat. Eén request om twee banners aan de parent-controller wordt uitgesplitst in twee requests om één banner voor de child-controllers.
Één controller die aangeroepen wordt door een HTTP request. In die Controller roep je vervolgens weer een andere controller aan voor het ophalen van de banners. Dus je moet twee controllers maken, en wellicht een aantal methods voor het returnen van één of meerdere banners zoals je nu doet met "getNumberOfBanners()".
En wat voor data krijg je in dit geval dan terug als je "getBannerById()" aanroept? Komt deze data uit de database? Krijg je HTML terug omdat hij de content van een View terug geeft?
In het eerste geval klopt het niet echt lijkt mij. Dat zou dan meer iets zijn wat thuis hoort in een Model layer.
In het tweede geval is het ook niet goed. Want je Views moeten bepalen wat voor (Sub)Views ze willen tonen. Dat is niet iets wat je in je Controller of waar anders dan ook wilt regelen. Eigenlijk helemaal niet met service code.
En dan komen we weer terug op mijn, volledig uitgelachen, subrequests die je vanuit je template moet aanroepen...
In class BannerManagementController() zou de methode getBannerById() uiteindelijk bijvoorbeeld een return new BannerController() kunnen opleveren. Heb je geen bannermanagement nodig, dan kan een new BannerController() ook zelfstandig worden gebruikt.
Het gaat hier even om de HMVC-gedachte: zodra je twee afhankelijke siblings hebt, hoort er een parent boven.
Het model splits je inderdaad ook MVC uit. Voor één banner heb je data van één banner nodig. Hogerop in de HMVC-hiërarchie heb je echter nog méér data en andere data, dus een ander/ruimer model. Daar formaliseer je beslissingsregels zoals "nooit meer dan drie banners per pagina" of "twee banners mogen nooit identiek zijn" of "adverteerder X moet 4 weken boven staan". Dat vereist een andere controller dan de controller die je gebruikt voor één banner.
Een ander, misschien beter voorbeeld is een blogpost met daaronder reacties. Er bestaat dan een één op veel-relatie tussen de blogpost en de reacties. Dat zijn alvast twee controllers, die samenhangen via de id van de blogpost. Boven die twee controllers heb je daarvoor opnieuw een HMVC-dispatcher nodig die de request om die specifieke id doorgeeft aan de controllers.
En ook hierbij speelt het model een rol, want je wilt bijvoorbeeld in het CMS kunnen instellen dat op bepaalde blogposts niet kan worden gereageerd. De parent-controller bepaalt dus via het model of de child-controller voor de reacties mag/moet worden ingezet.
De views van de HMVC-modules zijn dan ook logisch gescheiden, bijvoorbeeld:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- blogpost.tpl -->
<article id="blogpost">
...
</article>
<!-- reacties.tpl -->
<div id="reacties">
...
<ul>
<!-- reactie.tpl -->
<li class="reactie">...</li>
<!-- reactie.tpl -->
<li class="reactie">...</li>
...
</ul>
...
</div>
<article id="blogpost">
...
</article>
<!-- reacties.tpl -->
<div id="reacties">
...
<ul>
<!-- reactie.tpl -->
<li class="reactie">...</li>
<!-- reactie.tpl -->
<li class="reactie">...</li>
...
</ul>
...
</div>
Inclusief subviews dus. De lijst met reacties is één view waarbinnen elke reactie aan andere view is.
Gewijzigd op 21/02/2014 12:03:16 door Ward van der Put
Ward, maar het probleem is dat je dat nu vanuit de PHP code gaat bepalen. De PHP code heeft helemaal niks te zeggen over wat er op het scherm komt, alleen de view weet wat hij wilt gaan tonen. Je moet dus de subcontrollers aanroepen vanuit de template en niet vanuit de controller.
Heb je daar dan ook weer 2 verschillende Model layers voor? Moet ik die structuur dan ook echt zien als dit:
(Dit is maar een voorbeeld structuur)
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
\Modules
\Blog
\Controllers
BlogController.php
\Models
BlogModel.php
\View
blogpost.tpl
\BlogReacties
\Controllers
BLogReactieController
\Models
BlogReactieModel.php
\Views
reacties.tpl
\Blog
\Controllers
BlogController.php
\Models
BlogModel.php
\View
blogpost.tpl
\BlogReacties
\Controllers
BLogReactieController
\Models
BlogReactieModel.php
\Views
reacties.tpl
Zou dat er zo uit kunnen zien? Iedere "module" in een eigen mapje, met eigen controller, model en views?
Gewijzigd op 21/02/2014 12:16:22 door Wouter J
@Wouter:
>> Ward, maar het probleem is dat je dat nu vanuit de PHP code gaat bepalen. De PHP code heeft helemaal niks te zeggen over wat er op het scherm komt, alleen de view weet wat hij wilt gaan tonen.
Nee, dat denk ik niet. Volgens mij is de parent controller leidend. De parent controller zegt: ik wil dat module x y en z op deze pagina worden getoond. Deze modules hebben op hun beurt een eigen view.
Wat, hoe ik het begrijp, die parent controller in pseudo code dus doet is dit:
Daar is het probleem. Een controller mag nooit zeggen, ik wil dat dit wordt getoond. Een controller mag alleen zeggen, ik wil dat deze view wordt getoond. Die view bepaald vervolgens wat er precies wordt getoond.
Naar mijn idee horen reacties bij een blog. Om dan 2 models te maken waar je je data aan kan opvragen voor iets wat bij elkaar hoort is raar. Normaal gesproken zou je iets hebben van:
Code (php)
1
2
3
4
5
6
2
3
4
5
6
$blogModel = new BlogModel();
$blog = $blogModel->getBlogById(100);
// Returned een array waarin de reacties zitten.
// Elk item in die array kan een "Reactie" object zijn of ook weer een array
print_r($blog->Reacties());
$blog = $blogModel->getBlogById(100);
// Returned een array waarin de reacties zitten.
// Elk item in die array kan een "Reactie" object zijn of ook weer een array
print_r($blog->Reacties());
Dit is simpel en duidelijk.
Het is niet logisch om verschillende Models te gaan aanspreken om data te krijgen wat eigenlijk bij elkaar hoort.
Als je al views gaat includen vanuit je controller zoals Ozzie nu zegt, dan zit je sowieso verkeerd te denken. Alle View logica regel je vanuit een view en nergens anders. Ook wanneer je andere views wilt includen.
Wat ik bedoel, is dat de parent controller aangeeft dat op pagina foo de view van module x moet worden geplaatst. Die view van module x komt... uit module x. Hoe die view eruit ziet daar heeft de parent controller niks over te zeggen. Bedoelen we hetzelfde?
Toevoeging op 21/02/2014 13:40:30:
>> Als je al views gaat includen vanuit je controller zoals Ozzie nu zegt, dan zit je sowieso verkeerd te denken. Alle View logica regel je vanuit een view en nergens anders. Ook wanneer je andere views wilt includen.
Je moet zelf even anders leren denken. Dit is een andere manier dan jij gewend bent. Daar moet je je even voor openstellen in plaats van meteen te gaan roepen dat het niet klopt.
Je werkt met losse views. Iedere module heeft een eigen view. Als je dat principe begrijpt, dan zul je ook begrijpen dat het niet de bedoeling is om vanuit één zo'n view andere views te gaan aanroepen.