Hoi allemaal,

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:

<?php
$product_mapper = new ProductMapper();
$product = $product_mapper->load(12);
?>
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:

<?php

echo '<div id="product">' . $product->getName() . '</div>';

?>
Volgens die persoon moet je in de controller de benodigde variabelen doorsturen/beschikbaar maken in de view. Dus zoiets:

<?php
// ProductController

public function show() {
// product ophalen
$this->toView('product_name', $product->getName());
}

// view

echo '<div id="product">' . $product_name . '</div>';

?>
Zijn jullie het er mee eens dat je in de view geen objecten maar uitsluitend variabelen mag gebruiken?
Correct, maar er zijn ook overeenkomsten tussen alle pagina's. Strikt genomen zou je daarom de overeenkomsten ergens anders moeten onderbrengen dan de verschillen.

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.
Wat mij vooral opvalt als ik het zo doorlees is dat iedereen bij een template direct aan een volledige pagina denkt.

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.
>> Wat mij vooral opvalt als ik het zo doorlees is dat iedereen bij een template direct aan een volledige pagina denkt.

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 file="layout.html.twig"]<!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>[/code]
[code file="topic.html.twig"]{% 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 %}[/code]
[code file="tutorial.html.twig"]{% 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 %}[/code]
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:

<?php
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.
Dankjulliewel voor de vele reacties. Ik ga even proberen een paar puntjes aan te stippen.

@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:

<?php
$this->setTitle($product->getName());
?>
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?

<?php
foreach ($users as $user) {
echo $user->getName();
}
?>
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:

<?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>';

?>
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?
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.
>> Kun je aub een voorbeeldje geven?

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:
<?php
class PostViewModel
{
private $title;
private $content;

public function __construct($title, $content)
{
$this->title = $title;
$this->content = $content;
}

public function getTitle()
{
return $this->title;
}

public function getContent()
{
return $this->content;
}
}
?>
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.

[hr]

>> 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.
Ik weet zelf niet of het voorbeeld van Wouter helemaal juist is. Ik zou persoonlijk ook niet kiezen voor een ViewModel waar ik dingen doorgeef via de constructor. Ik zou gewoon de properties public maken en die direct setten. Ik ken ViewModels ook meer uit MVC.NET en die manier pas ik ook weer toe in mijn PHP code.

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:


<?php
class SimpleProductViewModel
{
    public $naam;
    public $prijs;
}
?>


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.
>> Ik zou persoonlijk ook niet kiezen voor een ViewModel waar ik dingen doorgeef via de constructor. Ik zou gewoon de properties public maken en die direct setten.

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.
@Frank:

>> 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?

Reageren