Na co použít actionXXX a renderXXX

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

Ahoj, stále moc nerozumím principům metod actionXXX a renderXXX v presenteru. Zkusím dát co nejjednodušší příklad. Dejme tomu, že mám redakční systém a na začátku chci nainstalovat databázi (vytvořit tabulky, vložit základní parametry, atd.), což mi provádí model Install::Application(). Metoda se buď provede správně, nebo vyhodí nějakou výjimku s popisem chyby. Teď k realizaci. Mám presenter InstallPresenter, model Install a tento kód:

try {
    Install::Application();
    $this->template->error = false;
} catch (Exception $ex) {
    $this->template->error = $ex; // nebo nějaké $ex->getMessage();
}

Použitá šablona pak buď vypíše chyby nebo oznámí úspěch. A teď otázka – kam tento kód patří?

  • Když tento kód dám do actionDefault, tak poruším pravidlo, že do template mám nastavovat v renderDefault().
  • Když ho dám do renderDefault(), tak se tam moc nebude hodit to Install::Application(). To je IMHO kód, který jednoznačně patří do actionDefault(). V budoucnu bych třeba mohl chtít po úspěšné instalaci hned přesměrovat atd.

V úvahu tak připadá nějaké „cachování“ výsledků v členské proměnné presenteru, něco jako:

private $installError;

public function actionDefault(){
    try {
        Install::Application();
        $this->installError = FALSE;
    } catch (Exception $ex) {
        $this->installError = $ex;
    }
}

public function renderDefault(){
    $this->template->error = $this->installError;
}

To se mi ale moc nelíbí, protože musím vytvářet navíc nějakou proměnnou čistě jen proto, abych přenesl jednu hodnotu z jedné metody do jiné – bez nějaké návaznosti na použitou třídu. Tedy zrovna v tomto případě proměnná installError docela zapadá do třídy InstallPresenter, ale dal by vymyslet příklad, kdy by to vypadalo dost podivně, kdy by to mělo daleko k reprezentaci stavu objektu. Navíc kdybych tam měl více proměnných, tak bych se upsal; ačkoliv to jde vyřešit __setem nebo nějakým v rámci objektu globálním úložištěm.

Jak to tedy řešit co možná nejefektivněji a nejčistěji?

(Poznámka: nejde mi čistě o tento příklad. Možná, že tento příklad má nějaké ještě jiné čistší řešení, jde mi čistě o vztah actionXXX a renderXXX, abych pochopil logiku těchto dvou metod. Metody jsem záměrně co možná nejvíce osekal.)

na1k
Člen | 288
+
0
-

Nevím jestli nebudu radit špatně, ale zrovna v případech, které popisuješ se osobně vůbec nebráním tomu, plnit šablony už v action. Možná by se to nemělo, ale beru to logicky – přeci si nebudu pro kažou blbinu přidávat pomocné proměnné jen na předání hodnoty z action do render.

Jinak se ale držím toho, že akce bez vizuálního výstupu (tj. všechny přesměrovávací a pomocné) dávám do actionXXX a všechny ostatní do renderXXX.

Rozdíl mezi action a render chápu v tom, že je každá jinde v životním cyklu presenteru, to je důležité. Nebo alespoň bylo. Teď už zbylo asi jen přesměrování, které v render nelze provést, protože už je pozdě. Dřív se v action (pokud si dobře pamatuju) registrovaly komponenty, zejména formuláře. Teď už to ale není aktuální, protože se všude používají továrničky.

newPOPE
Člen | 648
+
0
-

Tiez som mal tuto dilemu :-), pokial si dobre pamatam, tak na skoleni David hovoril, ze medzi tym nie je nejaky velky rozdiel… (okrem redirectu) takze v podstate vsetko mastim do action s nejaky log. usporiadanim (naco plnit sablonu ked sa potom redirectuje) render velmi nepouzivam.

Ako je spomenute vyssie vela veci pokryju tovarne.

Keby sa k tomuto vyjadril niekto kto vidi hlbsie by bolo super ;-)

Phoenix
Člen | 13
+
0
-

OK, díky za odpovědi, dokud se neobjeví nějaké jasné konvence, budu se řídit citem :-).

Editoval Phoenix (31. 7. 2010 11:42)

Blizzy
Člen | 149
+
0
-

Řešení je víc, pokud ti jde skutečně o čistotu kódu, je i to tvé druhé řešení s pomocnou členskou proměnnou celkem dobré, jen nepraktické v případě předávání více hodnot.

Já bych to řešil třeba takto (je ale potřeba mít jinak navrhnutý model):

private $installer;

public function getInstaller() {
    return isset($this->installer) ? $this->installer : new Installer;
}

public function actionDefault(){
    $this->getInstaller()->installApplication();
}

public function renderDefault(){
    $this->template->error = $this->getInstaller()->getErrors();
}

Samotná try a catch konstrukce by byla už v modelu, pokud vím, že chci odchytávat a zpracovávat vzniklé chyby. Je zachována myšlenka action a render, nejdřív data připravit, a pak je vykreslit (alespoň tak se tím řídím já). Jsem toho názoru, že render by nemělo řešit nic, co by mohlo selhat. Taky je potom kód presenteru přehledný, protože jsem část logiky přesunul jinam. Vyhnu se tím zbytečným proměnným, protože připravená data jsou uvnitř instance modelu, a ta se napříč životním cyklem zachová. Samotná metoda getInstaller taky nemusí být nutně v Presenteru (ale ve vyšší vrstvě), to závisí na konkrétní aplikaci a na tom, kde všude bude model používán…

Jak tohle napsat správně může být otázka taky osobního stylu programování, já sám moc statické metody u modelů nemusím a i když to možná někdy není stejně efektivní, radši vždy vytvářím instance. Taky se snažím více logiky přesunout do modelu, pokud to dává smysl, mám prostě rád jednodušší presentery. Někdo může zase upřednostnit jednodušší a rychlejší vývoj, složitější presentery nebo kvůli jednoduchosti naplnit šablonu už v execution fázi.

Podle mého názoru je můj způsob z hlediska návrhu čistší, ale může být pracnější nebo v některých situacích, hlavně jednoduché weby, overengineered (příliš robustní) řešení.

Editoval Blizzy (31. 7. 2010 12:23)

Phoenix
Člen | 13
+
0
-

Na tom tvém řešení se mi nelíbí, že v presenteru nemám jak reagovat na různé chyby, tedy na různé výjimky. Metoda installApplication() může při nezdaru vyhodit různé výjimky a na tyto různé výjimky bych mohl chtít různě reagovat v presenteru (jednou třeba přesměrováním, jednou vypsáním chybové hlášky apod.). Takže myslím, že „správněji“ by takové výjimky měly probublat až do presenteru, kde na ně mohu efektivně reagovat.

Jsem toho názoru, že render by nemělo řešit nic, co by mohlo selhat.

To je použitelná myšlenky, díky :-).

já sám moc statické metody u modelů nemusím a i když to možná někdy není stejně efektivní, radši vždy vytvářím instance

Jen tak pro pořádek – já v reálu také vytvářím instanci třídy, tady jsem to jen pro přehlednost zkrátit. Myšlenka mého kódu tím nijak neutrpěla :-).

Blizzy
Člen | 149
+
0
-

Phoenix napsal(a):

Takže myslím, že „správněji“ by takové výjimky měly probublat až do presenteru, kde na ně mohu efektivně reagovat.

Máš pravdu, to moje řešení jsem příliš zobecnil a není to moc dobře promyšlené, asi do toho problému tak nevidím.

Stejně jako ty jsi nezamýšlel pochytat všechny chyby, model taky nemusí chytnout všechno a zásadní výjimky může nechat probublat dál (aby se podle nich přesměrovalo, nebo předalo ErrorPresenteru). Jde možná taky o to určit co je v rámci konkrétního modelu skutečná výjimka, a co je „jen“ chyba. Možná je celý tento nápad blbost…

Editoval Blizzy (31. 7. 2010 13:42)

Phoenix
Člen | 13
+
0
-

Já ten kód zkusím ještě víc zobecnit a přidat jednu výjimku, ať je to jasnější:

try {
	$model->Method();
	$this->template->error = FALSE;
} catch (ExceptionA $ex) {
	$this->redirect('action');
} catch (ExceptionB $ex) {
	$this->template->error = $ex;
}

Jak tento kód co nejelegantněji přepsat do metod action a render?

Blizzy
Člen | 149
+
0
-

U tvého konkrétního případu s instalací jsem navrhl:
Rozhodnutí, které výjimky jsou skutečně závažné přesunout do modelu, skutečně zásadní výjimky mohou probublat dál. Chyby, které se budou vypisovat, jsou zapouzdřeny v instanci modelu, probublané výjimky můžeš zpracovat a pak třeba přesměrovat. V tomto řešení se ti nelíbilo to přesunutí režie do modelu, a chceš to mít raději v presenteru, s tím souhlasím, někdy to prostě do modelu nepatří.

Pokud teda chceš mít za každou cenu try, cache v presenteru, potom to bude v metodě action (důvod už jsem popsal výše). Buď se můžeš spokojit s tím, že nebudeš plnit template na jednom místě, nebo to řeš tou pomocnou proměnnou, jak jsi napsal v prvním příspěvku.

Pokud to podle tebe nepatří do stavu presenteru, potom patří do stavu modelu. Buď to můžeš udělat tím způsobem, jak jsem popsal dříve (přesunutí celého try-cache), nebo nějak zkombinovat, např.:

public function actionDefault(){
    try {
            $this->getInstaller()->installApplication();
    } catch (ExceptionA $ex) {
            $this->redirect('action');
    } catch (ExceptionB $ex) {
            $this->getInstaller()->addError($ex);
    }

}

public function renderDefault(){
    $this->template->error = $this->getInstaller()->getErrors();
}

Ono se špatně vymýšlí univerzální řešení použitelné na každou situaci. Mě přijdou ty chyby samotné jako speciální případ (protože jde o chytání výjimek), proto se mi moc nezdá tento argument:

Phoenix napsal(a):

kdybych tam měl více proměnných, tak bych se upsal

Těch víc proměnných můžeš přece uchovávat v tom modelu. Pokud to do něj nepatří, tak to zase patří do presenteru. Ta myšlenka globálního úložiště se mi nelíbí.


Phoenix napsal(a):

To se mi ale moc nelíbí, protože musím vytvářet navíc nějakou proměnnou čistě jen proto, abych přenesl jednu hodnotu z jedné metody do jiné – bez nějaké návaznosti na použitou třídu. Tedy zrovna v tomto případě proměnná installError docela zapadá do třídy InstallPresenter, ale dal by vymyslet příklad, kdy by to vypadalo dost podivně, kdy by to mělo daleko k reprezentaci stavu objektu.

Můžeš uvést ten nějaký konkrétní případ, u kterého chceš zachytávat výjimky v presenteru a předávat je do šablony, ale aby ty chyby zároveň nezapadali do té „reprezentace stavu“ presenteru? Můžeš u toho případu vymyslet i těch více proměnných, které musí být řešeny v presenteru a nemohou být uloženy jako data v instanci modelu? Podle mě by to pomohlo.

Editoval Blizzy (31. 7. 2010 14:32)

despiq
Člen | 320
+
0
-
<?php
$presenter->flashMessage();
?>