RFC: Komponenty které se samy nevykreslují, ale jen vracejí view

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
mystik
Člen | 295
+
+1
-

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
$_ctrl->renderSomething();

na

$_ctrl->view("something")->render();

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)

Šaman
Člen | 2640
+
0
-

Já už jsem to nestihl napsat do toho původního vlákna před víkendem. Nevim, k čemu přesně by to bylo dobré (ale je fakt, že komponenty a presentery netestuju), ale pohledy komponenty mají (ve smyslu několik render metod jedné komponenty).

mystik
Člen | 295
+
0
-

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
+
0
-

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 | 295
+
0
-

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 | 295
+
0
-

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)

bazo
Člen | 620
+
0
-

no len komponenty sa nemusia vzdy vykreslovat pomocou sablon

mystik
Člen | 295
+
0
-

bazo napsal(a):

no len komponenty sa nemusia vzdy vykreslovat pomocou sablon

Otázka je jestli to je dobře. Napadá tě případ, kdy by se komponenta nedala vykreslit pomocí šablony?

Editoval mystik (27. 5. 2014 9:28)

Vojtěch Dobeš
Gold Partner | 1316
+
0
-

Formulář.

Milo
Nette Core | 1283
+
0
-

Mám i malé komponenty, které se vykreslují stylem:

echo Html::el('img')...;

Podle mě to je dobře. Dá se to udělat i šablonou, ale je to zbytečná pruda.

mystik
Člen | 295
+
0
-

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

public function renderSomething() {
  return new TextView("...");
}

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 | 295
+
0
-

Nebo alternativně mít dva typy control. Control, který vrací jen text a ViewControl, který pracuje v View a šablonami. Vyhul bych se ale tomu aby Control sám něco vykresloval.

Editoval mystik (27. 5. 2014 11:55)

mystik
Člen | 295
+
+3
-

Napadlo mě alternativní řešení, které nebude takový zásah do frameworku a bude zpětně kompatibilní.

V latte by se nahradilo

<?php
$control->render()
?>

za

<?php
echo $control->render()
?>

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)

Felix
Nette Core | 1190
+
0
-

@mystik Nejvice se mi libil ten ControlView. Jestli ponese Template nebo jenom text nebo cokoli jineho s __toString() je uz zalezitost implementace.

Nechces to jeste preupravit? :-)

mystik
Člen | 295
+
0
-

@Felix: To se ale s touto úpravou nevylučuje. ControlView může být právě jedna z věcí, které se z control budou vracet.

akadlec
Člen | 1326
+
0
-

No a mergne to někdo do nette? Nebo to zase vyšumí? Docela se mě to líbí oproti klasickému echu co tam musí být :(

Felix
Nette Core | 1190
+
0
-

akadlec napsal(a):

No a mergne to někdo do nette? Nebo to zase vyšumí? Docela se mě to líbí oproti klasickému echu co tam musí být :(

Jo tak to tezko rict. Bylo by potreba nazoru @DavidGrudl, @FilipProcházka, @JanTvrdík apod..

akadlec
Člen | 1326
+
0
-

Tak vidím že PR je, tak by mohl teď někdo naplnit ty povzdechy z vedlejšího tématu a když už se něco do nette pokouší komunita přidat tak by to mohl i někdo zkontrolovat…

Jan Tvrdík
Nette guru | 2595
+
0
-

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 | 8172
+
+1
-

Jsem proti. Postupné vykreslování na výstup má svůj důvod.

duke
Člen | 650
+
0
-

Zrovna jsem chtěl napsat, že by nebylo špatné zeptat se autora, proč to navrhl zrovna tak, jak to navrhl, a zda k tomu neměl třeba nějaký rozumný důvod. Nyní již od něj víme, že k tomu důvod měl. Jen škoda, že to více nerozvedl. Zajímalo by mě, o jaký důvod šlo.

David Grudl
Nette Core | 8172
+
0
-

Důvod je ten, že postupné vykreslování je postupné ;-) Oproti sestavení a vrácení řetězce.

mkoubik
Člen | 728
+
0
-

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 | 295
+
0
-

@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 | 295
+
0
-

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

<?php
if(($view = $control->render()) !== NULL) { view(); }
?>

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)