RFC: Komponenty které se samy nevykreslují, ale jen vracejí view
- mystik
- Člen | 318
Tahle myšlenka už padla tady od @enumag: https://forum.nette.org/…ntipatternem#…
Nad stejnou věcí jsme přemýšlel už dřív, když jsem se poprvé trápil s testováním komponent.
Proč je vlastně vykreslení komponenty (její odeslání na výstup) přímo součástí komponenty? Je pro to nějaký důvod? Nebylo by lepší vykreslení oddělit a v zodpovědnosti render metody komponenty nechat pouze nastavení šablony – stejně jako to dělají presentery?
Jaké by to mělo výhody:
- komponenty by se snadněji testovaly – nemusím parsovat vyrenderované HTML nebo se nějak zvenku vrtat v komponentě abych zjistil co předala do šablony
- bylo by to jednotné s presentery
- oddělení zodpovědností
Jak by změna zhruba vypadala:
- implementace render metod v šabloně by vypadala stejně jako
v presenteru (pouze by se k šabloně přistupovalo přes
getTemplate()
) - v Control by se doplnila metoda pro provedení vykreslení řekněme
view($method, $params)
– ta by jenom zavolala příslušnou render metodu a vrátila šablonu - v implementaci makra control by se pak změnilo
na
Zatím mám jen hrubou představu jak by se to implementovalo a hlavně jestli by šla zachovat zpětná kompatibilita. Ale rád bych znal váš názor a rozpoutal diskuzi. Nevím jestli třeba současné řešení nevychází z nějakého návrhového rozhodnutí o kterém nevím. Pokud uznáte že to je dobrý nápad zkusil bych to připravit jako pull request.
Editoval mystik (26. 5. 2014 21:52)
- mystik
- Člen | 318
Ta hlavní myšlenka je aby komponenta sama nic nevypisovala na výstup, ale pouze předala ven šablonu, která se má vykreslit.
Pokud se komponenta sama přímo vykreslí tak nejde dobře testovat. A ani mi to nepřijde jako úplně čisté.
To že můžu mít více render metod s tímhle nijak zásadně nesouvisí, stejně to bude fungovat pro všechny render metody.
Editoval mystik (26. 5. 2014 23:08)
- enumag
- Člen | 2118
Podle mne by měla vracet něco jiného než šablonu, spíše nějaký ControlView objekt (v původním příspěvku jsem použil pro jednoduchost pole). Tento objekt by se pak předal šabloně k vykreslení. Snažím se tím docílit nezávislosti komponent na šablonách a hlavně na jejich konfiguraci.
Pozn. trochu jsem se inspiroval u symfoních formulářů.
- mystik
- Člen | 318
enumag napsal(a):
Podle mne by měla vracet něco jiného než šablonu, spíše nějaký ControlView objekt (v původním příspěvku jsem použil pro jednoduchost pole). Tento objekt by se pak předal šabloně k vykreslení. Snažím se tím docílit nezávislosti komponent na šablonách a hlavně na jejich konfiguraci.
Pozn. trochu jsem se inspiroval u symfoních formulářů.
Za mě ještě lepší řešení, ale to už by byl větší zásah do frameworku, který by asi nešel udělat zpětně kompatibilní.
Oproti tvému řešení bych ale nechal komponentě možnost nějak určit šablonu. V jednom jiném projektu (nepoužívá nette) používáme to, že presentery/komponenty vracejí identifikátor šablony, které je až pak následně přeložen na konkrétní šablonu (podle nastavené theme a případně dalších parametrů). Při vykreslování si ale můžeš v případě potřeby předat šablonu jinou.
Editoval mystik (26. 5. 2014 23:32)
- mystik
- Člen | 318
Když o tom přemýšlím tak ono by to asi šlo udělat zpětně kompatibilní. ControlView by implementoval ITemplate. V control by byla metoda createView() a deprecated metoda createTemplate(), která by jen předávala volání. V createView by se tvořila instance ControlView. ControlView by v render() nedělal nic – ta metody by byla jen pro zpětnou kompatibilitu. A tenhle view by pak šlo předat skutečné šabloně, které by si z něj přebrala cestu k souboru s výchozí šablonou a data a to vykreslila.
Editoval mystik (26. 5. 2014 23:38)
- mystik
- Člen | 318
@vojtech.dobes, @milo
Ok co v tom případě mít něco jako TextView do kterého přiřadíš jen obsah (content) a on se vykreslí pseudo-šablonou:
{$content}
Komponenta nebude o nic složitější:
Rozhraní bude identické a v případě potřeby můžu při vykreslování použít jinou šablonu. Např.:
<div class="special-div-needed-in-my-application-to-solve-graphic-glitch">
{$content}
</div>
To stejné pro formuláře.
Editoval mystik (27. 5. 2014 10:52)
- mystik
- Člen | 318
Napadlo mě alternativní řešení, které nebude takový zásah do frameworku a bude zpětně kompatibilní.
V latte by se nahradilo
za
Pro současné komponenty by se nic nezměnilo. Ty by rovnou vypsaly výstup
a nic nevracely takže echo
by nic nevypsalo.
Nové komponenty by ale mohly vracet string
(tím by se mnoho
nezměnilo), ale aspoň by se výstup nemusel chytat přes output buffer.
Nebo co je hlavní jakýkoliv objekt s __toString()
, který by
pak sloužil jako ControlView
. To by mohla být přímo
Template
, ale i nějaký vlastní objekt jehož obsah/typ můžu
snadno otestovat a zkontrolovat tak že komponenta dělá to co má.
Jediné změny by bylo přidání toho echo
.
PR: https://github.com/…tion/pull/88
Editoval mystik (29. 7. 2015 9:54)
- Jan Tvrdík
- Nette guru | 2595
Problém je, že rozhodnout, zda tohle v Nette chceme nebo ne, je výrazně složitější úloha, než to implementovat.
První, co mě napadá je, že tohle si snadno doimplementuješ sám, ne?
V komponentách budeš míst render()
používat
renderToView
, které bude vracet objekt a render()
pak
zavolá echo $this->renderToView()
.
- David Grudl
- Nette Core | 8258
Důvod je ten, že postupné vykreslování je postupné ;-) Oproti sestavení a vrácení řetězce.
- mkoubik
- Člen | 728
Ještě by mohla metoda render()
„vracet“ generátor a do
něj místo echa yealdovat data postupně. To si ale můžeš naimplementovat
sám, nebo v nějakém rozšíření, nemá cenu kvůli tomu zvedat požadavek
na php 5.5. Případně ve stylu symfony rovnou vracet nějakou streaming
response a do ní to posílat přes callbacky.
- mystik
- Člen | 318
@DavidGrudl Takže důvodem je optimalizace? Ok asi to někde má
význam pokud je obsah komponenty rozsáhlejší. Na druhou stranu tohle
řešení nijak nevylučuje že by se komponenty u kterých je ta optimalizace
důležitá vykreslili přímo v render
. Pouze umožní aby
u komponent, kde to postupné vykreslování důležité není je šlo lépe
testovat.
Alternativní řešení by bylo vracet tedy nějaký ten
ControlView
a na něj pak volat render
. To už by
znamenalo větší zásah do latte, ale nic složitého.
@JanTvrdík Ano jde to obejít a často to dělám podobně jak píšeš. Jde mi spíše o to, že aktuální řešení mi nepřijde úplně čisté i kdyby už jen proto že se dost složitě testuje.
Editoval mystik (31. 7. 2015 14:55)
- mystik
- Člen | 318
Alternativní nápad: Nevracet objekt nad kterým se zavolá echo, ale objekt
představující render funkci nad kterým by se zavolalo __invoke
.
Tím by se postupné vykreslování zachovalo, ale zároveň by se to dobře
testovalo.
Pak tam mít něco jako
Pak by ale bylo buď potřeba mít něco jako TempalteView
představujícíc render šablony nebo u šablony přidat __invoke
volající render
.
Editoval mystik (31. 7. 2015 15:52)