[oop] loading en object in view
Ik heb 2 OOP vragen, die enigszins met elkaar hebben te maken.
1) Loading
Als we een object hebben, bijvoorbeeld een product, dan kunnen we dat product laden met een mapper. Bijvoorbeeld:
Maar wat als we in plaats van één product bijvoorbeeld de 10 laatste toegevoegde producten willen tonen? Hoe werkt dat dan? Gebruiken we daar diezelfde mapper voor? Ik neem aan van niet, want we gaan niet ieder product stuk voor stuk uit de databse ophalen. Maar hoe dan wel?
2) Object in view
Stel ik heb een product ingeladen, hoe krijg ik dan de productinformatie in de view. Ik heb me ooit eens laten vertellen dat je in een view geen objecten mag gebruiken, omdat een view geen "intelligentie" mag bevatten. Dit zou volgens diegene dus niet mogen:
Volgens die persoon moet je in de controller de benodigde variabelen doorsturen/beschikbaar maken in de view. Dus zoiets:
Code (php)
Zijn jullie het er mee eens dat je in de view geen objecten maar uitsluitend variabelen mag gebruiken?
Gewijzigd op 12/05/2014 21:52:50 door Ozzie PHP
jouw eerste code mag wél. Waar je volgens mij mee in de war bent is het volgende:
je controller roept de view aan:
Code (php)
Je controller geeft alle data door in een array. nu wil je in de view de indexen uit de array omzetten in variabelen.
$data['product'] naar $product zeg maar
dat doe je met de php functie extract: http://nl1.php.net/extract
Toevoeging op 12/05/2014 22:32:24:
wat betreft vraag één verander load() gewoon in
- findAll() // geeft een array van objecten terug
- findOneBy(..) // geeft één object terug (of NULL indien niet gevonden)
- findBy(..) // geeft een array van objecten terug
Gewijzigd op 12/05/2014 22:37:36 door Frank Nietbelangrijk
Quote:
Maar wat als we in plaats van één product bijvoorbeeld de 10 laatste toegevoegde producten willen tonen? Hoe werkt dat dan? Gebruiken we daar diezelfde mapper voor? Ik neem aan van niet, want we gaan niet ieder product stuk voor stuk uit de databse ophalen. Maar hoe dan wel?
Sinds wanneer kan een mapper alleen stuk voor stuk dingen uit de database halen?
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
Views mogen best logica bevatten. De view mag van mij zelfs de content-type bepalen.
Templates wil je het liefst zo min mogelijk logica (en absoluut geen 'business logic') geven. Simpele if/else, while loops en dergelijken zijn prima. (google een naar 'logic less templates')
Houd controllers slank (het liefst niet meer dan 20-30 regels per method, inclusief whitespace en commentaar) en dom.
Models is waar de bulk van je logica zich bevind. Services zoals die mappers, de classes waar naartoe gemapt wordt, utility classes etc.
Views bevatten logica om de door de Model laag aangeleverde ruwe data - denk een ISO8601 geformatteerde datum, of een unix timestamp - op te juiste manier te weergeven - in het Nederlandse dd-mm-yyyy formaat. Daar komt logica aan te pas die niet in het model, maar ook niet in de template hoort.
Of HTML escapen: de Model laag hoort niet te weten hoe de data gebruikt gaat worden, de view wel. Al begint het nu een beetje onduidelijk te worden of je dit in de View of de template wilt hebben...
Gewijzigd op 12/05/2014 22:48:30 door Dos Moonen
>> jouw eerste code mag wél. Waar je volgens mij mee in de war bent is het volgende:
Het probleem is dat als ik een object in een view plaats, ik in principe ook dit zou kunnen doen:
Code (php)
1
2
3
4
2
3
4
<?php
echo '<div id="product">' . $product->getName() . '</div>';
$product->setProperty('foo', 'bar');
?>
echo '<div id="product">' . $product->getName() . '</div>';
$product->setProperty('foo', 'bar');
?>
Waarom ik dit in een view zou willen doen? Geen idee, maar het zou kunnen en daarom hoorde je geen objecten in een view te zetten aldus die persoon. In een view mag je alleen variabelen zetten. Dus dan zou je dus alle objecten weer moeten omzetten naar een array ofzo?
Code (php)
Dat lijkt me eerlijk gezegd een beetje dubbelop allemaal...
@Wouter:
>> Sinds wanneer kan een mapper alleen stuk voor stuk dingen uit de database halen?
Ah oke.. ik snap wat je bedoelt. Maar gaat de mapper dan niet de taak van het Model (als in MVC) overnemen?
@Dos:
>> Al begint het nu een beetje onduidelijk te worden of je dit in de View of de template wilt hebben...
Kun jij eens uitleggen hoe jij het verschil ziet tussen een view en een template? Ik ken eigenlijk (vanuit de 1e versie van Zend Framework) alleen het begrip view. Een view was dan een stuk HTML code waarin je de variabelen gebruikte die door de controller werden aangeleverd. Eén view was bijvoorbeeld de complete HTML voor een productpagina.
Gewijzigd op 13/05/2014 00:15:11 door Ozzie PHP
Ozzie PHP op 13/05/2014 00:11:03:
... en daarom hoorde je geen objecten in een view te zetten aldus die persoon.
Ik weet dat symfony of twig en codeigniter het zo doen. Het alternatief is alles in een array zetten. Beetje omslachtig niet? Bovendien geef je alleen de entities mee naar de view. Niet de mappers. en dan nog de waarde van een array kun je ook veranderen in de view. Nee voor mij geen enkel probleem en nogmaals de grote frameworks doen het zo.
>> Nee voor mij geen enkel probleem en nogmaals de grote frameworks doen het zo.
Oké, even om alle misverstanden uit de weg te ruimen... met "doen het zo" bedoel je het gebruik van objecten in views, correct?
Code (php)
Daarmee ligt het ontwerp van de productpagina erg vast — en misschien wel té vast. Bovendien kunnen we de template alleen gebruiken voor een productpagina, niet voor andere soorten pagina's.
Die beperkingen hebben we niet als we er een controller tussen zetten die bepaalt dat $title = $product->getName(). Dan kunnen we $title namelijk ook op iets anders instellen. En dan kunnen we de template ook hergebruiken voor iets anders dan alleen een productpagina:
Code (php)
Ozzie PHP op 13/05/2014 00:11:03:
Kun jij eens uitleggen hoe jij het verschil ziet tussen een view en een template? Ik ken eigenlijk (vanuit de 1e versie van Zend Framework) alleen het begrip view. Een view was dan een stuk HTML code waarin je de variabelen gebruikte die door de controller werden aangeleverd. Eén view was bijvoorbeeld de complete HTML voor een productpagina.
Je kunt min of meer zeggen dat een template een vorm van een view is. Meestal gebruik ik één template voor één webpagina (eventueel aangevuld met universele templates voor de masthead, de sitenavigatie en een footer). Daarbinnen kunnen paginaonderdelen echter worden opgebouwd met verschillende views. Een voorbeeld zijn advertenties: wil je drie advertenties weergegeven, dan hergebruik je drie keer hetzelfde onderdeel (drie keer één view) op drie posities in één template (een andere view).
Ward:
En dan kunnen we de template ook hergebruiken voor iets anders dan alleen een productpagina:
Waarom zou je dat willen? Een productpagina en een blogpagina hebben beide een compleet andere lay-out, die krijgen ook mooi hun eigen view. :)
Ozzie:
Maar gaat de mapper dan niet de taak van het Model (als in MVC) overnemen?
De DataMapper is de Model. Dit werkt weer precies zoals het template <> view verhaal. "Model" is de naam van een laag in je applicatie, "DataMapper" is een invulling van die laag. "View" is de naam van een laag in je applicatie, "Template" is een invulling van die laag.
Wouter J op 13/05/2014 09:17:47:
Waarom zou je dat willen? Een productpagina en een blogpagina hebben beide een compleet andere lay-out, die krijgen ook mooi hun eigen view. :)
Ward:
En dan kunnen we de template ook hergebruiken voor iets anders dan alleen een productpagina:
Waarom zou je dat willen? Een productpagina en een blogpagina hebben beide een compleet andere lay-out, die krijgen ook mooi hun eigen view. :)
Als jij het okay vindt dat www.example.com een compleet andere lay-out heeft dan www.example.com/blog, dan is dat jouw persoonlijke ontwerpbeslissing. Over smaak valt niet te twisten :-)
Gewijzigd op 13/05/2014 09:38:34 door Ward van der Put
Lay-out gaat niet over smaak, dat is design :) Kijk alleen naar deze site. Deze forum topic pagina is toch qua lay-out anders dan een tutorial pagina of de nieuws pagina?
De vraag van Ozzie naar verschillen en overeenkomsten tussen templates en views is daarom wel een interessante.
Persoonlijk vind ik overigens ook dat de term template vaak verkeerd wordt gebruikt, want bijvoorbeeld "templates" die je voor open-source CMS'en en blogtools kunt downloaden of kopen, zijn vaak meer skins dan echte templates. Als je het design kunt veranderen door een ander CSS-bestand te gebruiken, dan is dat een skin, geen template.
Ik vind het persoonlijk bevoorbeeld wel prettig om mijn pagina in secties in te delen (menu header content om het even simpel te houden), en dan 3 templates te schrijven die ik elk binnen 1 pagina los inlaad.
Dat valt mij inderdaad ook op :)
Ward, de overeenkomstige elementen breng je onder in een "parent" template. De verschillen in andere templates. Even een Twig voorbeeldje:
Code (layout.html.twig) (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html ...>
<head>
<title>{% block title %}{% endblock %} - PHPhulp</title>
</head>
<body>
<header>...<header>
<div id="page__main">
{% block main %}{% endblock %}
</div>
<footer>...</footer>
</body>
</html>
<html ...>
<head>
<title>{% block title %}{% endblock %} - PHPhulp</title>
</head>
<body>
<header>...<header>
<div id="page__main">
{% block main %}{% endblock %}
</div>
<footer>...</footer>
</body>
</html>
Code (topic.html.twig) (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
{% extends 'layout.html.twig' %}
{% block title %}{{ topic.title }}{% endblock %}
{% block main %}
<article>
<h1>{{ topic.title }}</h1>
<p>{{ topic.content }}</p>
<ul>
{% for comment in topic.comments %}
<li>
<article>
<h1>{{ comment.author }}</h1>
<p>{{ comment.content }}</p>
</article>
</li>
{% endfor %}
</ul>
</article>
{% endblock %}
{% block title %}{{ topic.title }}{% endblock %}
{% block main %}
<article>
<h1>{{ topic.title }}</h1>
<p>{{ topic.content }}</p>
<ul>
{% for comment in topic.comments %}
<li>
<article>
<h1>{{ comment.author }}</h1>
<p>{{ comment.content }}</p>
</article>
</li>
{% endfor %}
</ul>
</article>
{% endblock %}
Code (tutorial.html.twig) (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
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
{% extends 'layout.html.twig' %}
{% block title %}{{ tutorial.title }}{% endblock %}
{% block main %}
<article>
<h1>{{ tutorial.name }}</h1>
<p>{{ tutorial.description }}</p>
<nav>
<ul>
{% for page in tutorial.page %}
<li><a href="{{ page.link }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
</nav>
<aside>
<h1>Comments</h1>
<ul>
{% for comment in tutorial.comments %}
{# ... #}
{% endfor %}
</ul>
</aside>
</article>
{% endblock %}
{% block title %}{{ tutorial.title }}{% endblock %}
{% block main %}
<article>
<h1>{{ tutorial.name }}</h1>
<p>{{ tutorial.description }}</p>
<nav>
<ul>
{% for page in tutorial.page %}
<li><a href="{{ page.link }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
</nav>
<aside>
<h1>Comments</h1>
<ul>
{% for comment in tutorial.comments %}
{# ... #}
{% endfor %}
</ul>
</aside>
</article>
{% endblock %}
Gewijzigd op 13/05/2014 10:36:38 door Wouter J
Ozzie PHP op 13/05/2014 00:11:03:
Het probleem is dat als ik een object in een view plaats, ik in principe ook dit zou kunnen doen:
Code (php)
1
2
3
4
2
3
4
<?php
echo '<div id="product">' . $product->getName() . '</div>';
$product->setProperty('foo', 'bar');
?>
echo '<div id="product">' . $product->getName() . '</div>';
$product->setProperty('foo', 'bar');
?>
Het "originele" object moet je niet doorgeven aan je view. Daar heb je dan ook ViewModels voor.
En ViewModels mogen gewoon objecten bevatten (andere ViewModels) or array en dergelijke. Daar is niets mis mee.
@Ward:
Ik begrijp jouw voorbeeld met $title. Ik neig dan echter naar de oplossing van Wouter, waarbij je gebruik maakt van een "master" view, waar al een titel in is verwerkt. En dan zou je zelfs vanuit de controller een aparte functie kunnen hebben om een titel in te stellen:
Maar er komt een punt op de pagina waar je iets specifieks (een product, een nieuwsbericht, een user enz.) wil uitwerken. Stel je hebt bijvoorbeeld een overzicht met de 10 laatste toegevoegde Users. Iedere User heeft een naam. Dan staan er dus 10 namen op je scherm, en die namen zijn natuurlijk geen titels. Haal je zo'n naam nu direct uit het originele User object is dan mijn vraag?
Is dat correct?
@Wouter:
>> De DataMapper is de Model. Dit werkt weer precies zoals het template <> view verhaal. "Model" is de naam van een laag in je applicatie, "DataMapper" is een invulling van die laag. "View" is de naam van een laag in je applicatie, "Template" is een invulling van die laag.
Oké. Het lastige vind ik dus om te begrijpen hoe dit er in de code (even heel versimpeld) uitziet. In Zend Framework 1.0 kreeg je zeg maar zoiets:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
// FooController.php
public function showFoo() {
$foo_model = new FooModel();
$foo = $foo_model->getFoo();
$this->toView('name' , $foo->getName());
$this->toView('description', $foo->getDescription());
$this->renderView('foo_view');
}
// foo_view.phtml
echo '<div id="foo">';
echo $name . '<br><br>' . $description;
echo '</div>';
?>
// FooController.php
public function showFoo() {
$foo_model = new FooModel();
$foo = $foo_model->getFoo();
$this->toView('name' , $foo->getName());
$this->toView('description', $foo->getDescription());
$this->renderView('foo_view');
}
// foo_view.phtml
echo '<div id="foo">';
echo $name . '<br><br>' . $description;
echo '</div>';
?>
Je had dan dus eigenlijk 3 bestanden:
- FooController.php
- FooModel.php
- foo_view.phtml
Hoe ziet deze opzet er dan uit met een mapper en een template? Blijft dat gewoon hetzelfde, maar noem je het FooModel.php nu ineens FooMapper.php?
@D Vivendi:
>> Het "originele" object moet je niet doorgeven aan je view. Daar heb je dan ook ViewModels voor.
Die opzet ken ik niet. Kun je aub een voorbeeldje geven?
Gewijzigd op 13/05/2014 17:41:35 door Ozzie PHP
Ozzie PHP op 13/05/2014 00:59:51:
Oké, even om alle misverstanden uit de weg te ruimen... met "doen het zo" bedoel je het gebruik van objecten in views, correct?
Correct Ozzie.
Als we het heel domain driven aanpakken gebruik je DTO's (Data Transer Objects) om tussen verschillende lagen te communiceren. Voor applicatie -> view gebruik je hiervoor ViewModels:
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Voor je template zet je je Page entity dus om in een PageViewModel.
Toevoeging op 13/05/2014 18:47:26:
Maar merk op dat Domain Driven Development op dit niveau alleen maar leuk wordt als je echt met grote complexe projecten gaat werken. Voor bijna alle gevallen is dit veel te overdreven, zeg je even KISS en You Aint Gonna Need It en gooi je gewoon je entity in de view.
>> Blijft dat gewoon hetzelfde, maar noem je het FooModel.php nu ineens FooMapper.php?
Het grappige is dat een FooModel eigenlijk al een DataMapper is. Alleen noem je het Model, omdat Zend je nou eenmaal ooit heeft geleerd het Model te moeten noemen.
Gewijzigd op 13/05/2014 19:52:11 door Wouter J
In .NET is het bijvoorbeeld zo dat elke view zijn eigen Model/ViewModel heeft. Zelfs partial views.
Zo'n ViewModel is niets meer dan slechts een simpele class met de properties die jij beschikbaar wilt stellen aan je view.
Bijvoorbeeld:
Als je alleen die twee properties wilt tonen uit de hele lijst properties die komen uit je "ProductModel" class.
Je ViewModel set je in je controller method.
Overigens zou ik helemaal geen ViewModels of wat voor php objecten dan ook aan mijn view mee willen geven. Ik zou alles liever via REST doen. Maar goed, dat is dan weer een ander verhaal denk ik.
Als je echt meer over view models in .NET wilt weten zou ik gewoon even op youtube wat tutorials bekijken. Dat is denk ik de beste manier om hierover een heldere uitleg te krijgen.
Hier heb ik ook over gedacht. Voor Command objects doe ik dit namelijk vaak wel :) Maar hierdoor raak je in je View wel een beetje beveiliging kwijt: De view kan de data aanpassen. En goed, de view kan alleen de data op view level aanpassen en niet op applicatie level, maar toch je hebt de ViewModel gemaakt om een immutable object te hebben.
>> Correct Ozzie.
Oké, thanks ;)
@Wouter:
>> Het grappige is dat een FooModel eigenlijk al een DataMapper is. Alleen noem je het Model, omdat Zend je nou eenmaal ooit heeft geleerd het Model te moeten noemen.
Oké... dus Model en Mapper zijn gewoon hetzelfde?
In je voorbeeld heb je het over een PageViewModel en in het codevoorbeeld over een PostViewModel. Is een van beide een verspreking?
Ik snap alleen nog niet hoe het werkt zo'n DTO. Stel je wil een aantal producten tonen. Is dan de bedoeling dat de productgegevens uit de database haalt? Dat je hier normale productobjecten van maakt, en dat je dan vervolgens aan de hand van deze objecten een array gaat maken met speciale ProductViewModels, die in feite hetzelfde zijn als de normalen Product objecten, maar dan zonder setters? Begrijp ik het dan goed?
En jij geeft zelf al aan dat je KISS zou toepassen. Jij bent dus ook voorstander om gewoon de productobjecten zelf in een view te gebruiken?
>>Hier heb ik ook over gedacht. Voor Command objects doe ik dit namelijk vaak wel :) Maar hierdoor raak je in je View wel een beetje beveiliging kwijt: De view kan de data aanpassen. En goed, de view kan alleen de data op view level aanpassen en niet op applicatie level, maar toch je hebt de ViewModel gemaakt om een immutable object te hebben.
Maar als ik een normaal Product object in mijn view gebruik, dat kan ik toch in de view via de setter methods toch ook de data aanpassen? Of begrijp ik het nu verkeerd?
@D Vivendi:
Oké... ik vraag me af, wat jij noemt een ProductModel noem ik volgens mij gewoon een Product class (die in mijn library staat). Waarom noem je dat een model?