Formulář jako komponenta – onSuccess nefuguje

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

Ahoj,
Experimetuji s komponentami a tak jsem si vytvořil komponentu, která má na starosti formulář.
Chci, abych neměl v presenteru žádnou zbytečnou metodu… Takže jsem si přepsal metodu createComponent v BasePresenteru.

	protected function createComponent($name) {
		$component = parent::createComponent($name);

		if ($component === NULL) {
			$className = 'App\\Components\\'.$name;
			if(class_exists($className))
				$component = new $className($this, $name);
			else
				return NULL;
		}

		return $component;
	}

A chtěl bych v komponentě kompletně obsluhovat model. (Jak do komponenty dostanu bezpracně model? – Nejhezčí by mi přišlo @inject…)

To chci využít v metodě zaregistrované v onSuccess ($form->onSuccess[] = $this->processForm;). Když odešlu formulář tak se mi jenom znova vykreslí ten samý formulář – nic se nestane. Kde je chyba?

Ještě: Formulář vytvářím v render(…)

Šaman
Člen | 2666
+
0
-
  1. Ukázku jednoduché formulářové komponenty máš tady
  2. Takhle vypadá řešení pomocí automaticky generovaných továrniček. Pokud je nebudeš používat, tak interface (ILogin…) ignoruj a to, co je v traitě (TCreate…) patří do presenteru.
  3. Do komponent všechno musíš předat přes konstruktor, jestli chceš aby se to předávalo samo pomocí autowiringu. Případně setterem, ale pak musíš v továrničce tento setter nastavit.
Zax
Člen | 370
+
-1
-

Dokážu si představit, že někdo chce používat @inject v komponentách. Je to úplně stejný problém jak u presenterů (Base třída a závislosti, konstruktor hell) a naštěstí tu je možnost @inject zapnout (aspoň tedy pro komponenty generované továrničkami). Sám to už pár dní používám a zatím si spokojeně chrochtám :-) Akorát to není čisté řešení, porušuje zapouzdřenost, ale pokud to někdo chce, ta možnost tu je ;-)

Editoval Zax (1. 6. 2014 23:04)

Šaman
Člen | 2666
+
0
-

Zax napsal(a):

Dokážu si představit, že někdo chce používat @inject v komponentách. Je to úplně stejný problém jak u presenterů (Base třída a závislosti, konstruktor hell) a naštěstí tu je možnost @inject zapnout (aspoň tedy pro komponenty generované továrničkami). Sám to už pár dní používám a zatím si spokojeně chrochtám :-)

Ano, zapnout to jde. Ale dokud nemá potřebu to zapínat, tak bych ho neučil způsob, který není podporovaný frameworkem. Myslím, že nehledá způsob, jak zapnout injectování ala presenter, ale teprve dělá první pokusy jak Nette vlastně funguje.

A propo dopiš prosímtě k tomu řešení varování, že anotace @inject je prasárna, která porušuje základní pravidlo OOP a to zapouzdření. Osobně doporučuji v abstraktních třídách používat inject metody, které jsou v pohodě. Presenter je z několika důvodů trochu zvláštní, takže použití anotace @inject se tam snese, ale jen když člověk ví, že tak trochu prasí. Rozhodně by si programátor neměl myslet, že je to čisté.

cactux
Člen | 12
+
0
-

Mě se @inject tak líbí, že jsem si to zajistil při přepisování metody v basePresenteru:

	protected function createComponent($name) {
		$component = parent::createComponent($name);

		if ($component === NULL) {
			$className = 'App\\Components\\'.$name;
			if(class_exists($className)) {
				$component = new $className($this, $name);
				$injectProps = Nette\DI\Helpers::getInjectProperties(\Nette\Reflection\ClassType::from($component));
				foreach ($injectProps as $name => $type) {
					$component->$name = $this->context->getByType($type);
				}
			} else
				return NULL;
		}

		return $component;
	}

@Šaman : Ano, není to čisté – jsem si toho vědom. A to mě opravňuje k tomu to používat.

(„První pokusy“ dělám už dva roky…)

Šaman
Člen | 2666
+
0
-

Ok, bral jsem to podle počtu příspěvků. A podle toho „formulář vytvářím v render“. To je pozdě, protože po odeslání neexistuje formulář, který by přijal výsledky.. ten se vytvoří až při vykreslování.

Editoval Šaman (1. 6. 2014 22:37)

Zax
Člen | 370
+
-1
-

@Šaman: OK příspěvek jsem upravil. Nechci flejmovat, je to prasení, ale ne všichni mají akademickou čistotu jako hlavní preferenci. @inject anotace v komponentách má i své výhody a navíc cactux dal přímo jasně najevo, že má zájem o anotace ;-)

Mesiah
Člen | 240
+
-1
-

Mimochodem, off topic, tím, že do komponenty vkládáš model přicházíš o znovupoužitelnost, nepletu-li se…? Není to škoda?

Oli
Člen | 1215
+
0
-

Pokud má ten model využití, tak ho tam vložit musíš. Znovu použitelné to je, jen deklaruješ: „Aby jsi mě mohl použít, tak mě dej tenhle model“. Např. když máš komponentu, která vypisuje všechny články, ale ty jí nepředáš model/články, tak nemá co vypisovat a je zbytečná. A může být znovupoužitelná jak chce. :-)

Nebo jsi to myslel jinak?

cactux
Člen | 12
+
0
-

Představme si klasickou situaci: Máme blog s články. Máme komponentu, která má na starosti vykreslení článku. A teď jde o to jak ten článek tam dostaneme:

  • Můžeme ho předat v templatu, tam ho dostat z presenteru a tam z modelu. Dostaneme tedy text (autora, datum vydání atd.) jako paramatr metody render() komponenty. Ten potom pošleme rovnou do templatu komponenty a je to.
  • Můžeme komponentě předat id článku a model a komponenta sama si vytáhne text (atd.) z modelu.

Mě se osobně zamlouvá více druhá varianta protože asi nebudeme vykreslovat článek, který není v db a kód tak rozdělíme do více míst (tj. nebude obrovský presenter). ⇒ Bude to přehlednější.

Možná se ptáte na znovupoužitelnost. Když se kouknete na phpFasion.com, tak uvidíte, že články se vykreslují jak za sebou na homepage tak na stránce článku

Zax
Člen | 370
+
0
-

U druhé varianty bych se právě bál těch výpisů. Když komponenta dostane IDčko a vytáhne si jeden řádek, co potom udělá cyklus, kde vypisuješ seznam deseti článků? Nejspíš to bude 10 dotazů do databáze…

První varianta tento problém nemá, ale je fakt, že se mi taky víc zamlouvá ta druhá varianta.

Osobně to řeším tím, že seznam a detaily (zobrazení jednoho článku) dělím do dvou komponent. Můžeš mít pro ně společnou sub-template, kterou si naincluduješ do hlavní šablony, aby se ti to zobrazovalo stejně.

David Matějka
Moderator | 6445
+
0
-

moznost 1, tedy predani do renderu v sablone, neni idealni – v komponente pak nebudou korektne fungovat snippety.

Ja pouzivam moznost 3 – do komponent temer vzdy predavam konkretni entitu, kterou si vytvorim v presenteru (respektive na urovni, kde vytvarim onu komponentu)

cactux
Člen | 12
+
0
-

matej21 napsal(a):

Ja pouzivam moznost 3 – do komponent temer vzdy predavam konkretni entitu, kterou si vytvorim v presenteru (respektive na urovni, kde vytvarim onu komponentu)

V presenteru získáš entitu a potom jí přes šablonu dáš do komponenty? Nebo ji injectuješ přímo z presenteru (a jak?)?

Oli
Člen | 1215
+
-2
-

komponenta se vytvori ve chvíli, kdy ji poprvé zavoláš. Takže můžeš udělat něco jako

/** @var ArticlesComponent @inject */
private $articlesComponent;

public function createComponentArticles()
{
	return $this->articlesComponent;
}


public function actionDetail($id)
{
	$article = $this->articleRepository->getById($id);
	$this['articles']->setArticle($article);
}
David Matějka
Moderator | 6445
+
+3
-

priklad:
Komponenta, ktera v konstruktoru prijima entitu:

class SomeArticleControl extends Control
{
	public function __construct(Article $article, Dependency1 $dep1, ...)
	{
		...
	}
}

Factory:

interface SomeArticleControlFactory
{
	/**
	* @param Article $article
	* @return SomeArticleControl
	*/
	public function create(Article $article);
}

Presenter:

class FooArticlePresenter extends BasePresenter
{
	/** @var Article */
	protected $article;

	/** @var SomeArticleControlFactory @inject */
	public $someArticleControlFactory;

	public function actionDefault($id)
	{
		if(!$this->article = $this->findArticle($id)) {
			$this->error();
		}
	}

	protected function createComponentSomeArticleControl()
	{
		return $this->someArticleControlFactory->create($this->article);
	}
}

Jen musim registrovat factory v neonu (nebo v compiler extension), coz muze byt s parametrama v nekterych verzich nette trosku wtf. Tohle reseni by melo fungovat vzdy:

services:
	-
		class: SomeArticleControl
		implement: SomeArticleControlFactory
		parameters: [Article $article]
		arguments: [%article%]
		autowired: true

od 2.2 neni nutno uvedet autowired: true, stejne jako jiz neni nutno uvadet znovu class, kdyz uz je v return anotaci. Pokud je to registrovany v compiler extension, neni nutno ani uvadet parametry, pouze argumenty. Typehint parametru v neonu byt musi (pokud je ve factory) a musi to byt FQN. Proto radeji registruju factory v compiler extension :)

Editoval matej21 (3. 6. 2014 16:55)

cactux
Člen | 12
+
0
-

Oli napsal(a):

komponenta se vytvori ve chvíli, kdy ji poprvé zavoláš. Takže můžeš udělat něco jako

/** @var ArticlesComponent @inject */
private $articlesComponent;

public function createComponentArticles()
{
	return $this->articlesComponent;
}


public function actionDetail($id)
{
	$article = $this->articleRepository->getById($id);
	$this['articles']->setArticle($article);
}

Tohle he možná nejhezčí, ale já bych předával jenom id a data tahal až v komponentě… (argumenty dříve).

A nelíbí (=myslím že by se mohlo udělat lépe) se mi ta jednořádková metoda createComponentArticles a ta property (Neměla by být public?)…
Jsou tam potřeba? (I v tom tvém řešení) Nefungovalo by to stejně bez nich? … (po chvílí) … Vlastně já si přepisuju petodu createComponent tak, že se to dělá samo :D (viz dříve)

Mesiah
Člen | 240
+
0
-

Spíše jsem měl na mysli situaci komponenta jako formulářový prvek – pokud bych obsluhu callbacku (a přiznám se, že ve většině svých projektů to tak skutečně mám) nechal přímo v komponentě – tedy sama komponenta ví, jak se v aplikaci pracuje s entitami a na můj vkus je tam moc velká provázanost s konkrétním modelem aplikace, takže pokud bych udělal ctrl-c/ctrl-v s adresářem komponenty, pak musím upravovat vnitřní logiku, a naopak, pokud poskytnu komponentu čistě jako komponentu, tedy bez nějakých vnitřních business procesů a přinutím implementátora napsat vlastní obsluhu, pak považuji komponentu za nezávislou a dobře znovupoužitelnou.
Ohledně komponenty na vypisování článků, ty u sebe řeším předáváním entit, ať už jde o kolekci, či jeden prvek – v práci (ASP.NET MVC) nám jeden kolega (teď už bývalý) dělal modul na rendering sestav a použil princip předávání ID a komponenta sama si entity dotáhne, ale ukázalo se to, jako hodně neefektivní řešení (ačkoli se data, dotahovaná z db cachují) a mě osobně to přijde i jako jít proti ideologii aplikace, bo když už využíváme MEF a princip dependency injection, tak jej používejme všude a pokud nějaká data mi ten kdo daný modul používá neposkytne, tak prostě vyhoď exception (btw, kolega nevyhazuje exception, ale vrací null – hledat proč se sestava nevyrenderovala, ale vrátila null je fakt boží).

Oli
Člen | 1215
+
0
-

cactux napsal(a):

Tohle he možná nejhezčí, ale já bych předával jenom id a data tahal až v komponentě… (argumenty dříve).

A nelíbí (=myslím že by se mohlo udělat lépe) se mi ta jednořádková metoda createComponentArticles a ta property (Neměla by být public?)…
Jsou tam potřeba? (I v tom tvém řešení) Nefungovalo by to stejně bez nich? … (po chvílí) … Vlastně já si přepisuju petodu createComponent tak, že se to dělá samo :D (viz dříve)

Máš pravdu v podstatě ve všem. Jen jsem se snažil nastínit řešení. Řešení, které plus/minus používám taky popsal lip (a podrobnějc) @matej

cactux
Člen | 12
+
0
-

Oli napsal(a):

Máš pravdu v podstatě ve všem. Jen jsem se snažil nastínit řešení. Řešení, které plus/minus používám taky popsal lip (a podrobnějc) @matej

To mi přijde moc složitý… Pokud bych chtěl mít hodně komponent (jakože chci) tak se utopím v továrničkách (a takové snečištění ovzduší… :D ).

Filip Procházka
Moderator | 4668
+
+1
-

To cos udělal s tou createComponent se tady na fórum vrací pravidelně jako chřipka. Je to anti-pattern. Měl bys každé komponentě kterou budeš v presenteru potřebovat, napsat extra továrnu.

A navíc ty injecty děláš špatně, správně takto

$object = $this->context->createInstance($class);
$this->context->callInjects($object);

A vůbec, správně to tady má jenom matej21, byť možná zbytečně složitě, entitu bych předával raději setterem (má to víc výhod než jenom jednodušší konfiguraci).

Zax napsal(a):

@inject anotace v komponentách má i své výhody a navíc cactux dal přímo jasně najevo, že má zájem o anotace ;-)

Nemá to žádné výhody a přestaň prosím lidem radit prasárny.

Konstruktor hell v komponentách neexistuje a pokud ho tam máš tak to děláš špatně.

Mesiah napsal(a):

Mimochodem, off topic, tím, že do komponenty vkládáš model přicházíš o znovupoužitelnost, nepletu-li se…? Není to škoda?

Pleteš.

Oli napsal(a):

komponenta se vytvori ve chvíli, kdy ji poprvé zavoláš. Takže můžeš udělat něco jako

/** @var ArticlesComponent @inject */
private $articlesComponent;

public function createComponentArticles()
{
	return $this->articlesComponent;
}


public function actionDetail($id)
{
	$article = $this->articleRepository->getById($id);
	$this['articles']->setArticle($article);
}

Tohle je taky brutální prasárna, komponenty nejsou služby!

cactux napsal(a):

Tohle he možná nejhezčí, ale já bych předával jenom id a data tahal až v komponentě… (argumenty dříve).

A co když dostaneš neexistující IDčko? Jak pošleš 403 ze šablony?


A naučte se prosím citovat, opravil jsem vám ty příspěvky, není nutné kopírovat stejný text 10×.

Zax
Člen | 370
+
0
-

Filip Procházka napsal(a):

Zax napsal(a):

@inject anotace v komponentách má i své výhody a navíc cactux dal přímo jasně najevo, že má zájem o anotace ;-)

Nemá to žádné výhody a přestaň prosím lidem radit prasárny.

Chtěl inject, tak proč mu neporadit? Mně se inject líbí, mám to napsané hnedka a funguje to. Jo, když do toho někdo sáhne zvenku a přepíše to, je to v pr., ale úplně to samé platí o presenterech a ty jsou úplně stejně tak součástí Nette jako komponenty (dokonce mají oba stejnou Base class – PresenterComponent). Proč tedy u jednoho je to dokonce doporučovaný způsob injectování závislostí a u druhého prasárna? WTF?

Konstruktor hell v komponentách neexistuje a pokud ho tam máš tak to děláš špatně.

Base control, který chce továrnu na komponentu pro flash messages. Naprosto validní požadavek (2/3 mých komponent potřebujou zobrazovat flashky). Bez traitů. DRY a KISS. Go! ;-)

Editoval Zax (3. 6. 2014 19:22)

Filip Procházka
Moderator | 4668
+
0
-

Zax napsal(a):

Chtěl inject, tak proč mu neporadit? Mně se inject líbí, mám to napsané hnedka a funguje to.

Tak si to používej ale neraď to ostatním.

Proč tedy u jednoho je to dokonce doporučovaný způsob injectování závislostí a u druhého prasárna? WTF?

Base control, který chce továrnu na komponentu pro flash messages. Naprosto validní požadavek. Bez traitů. DRY a KISS. Go! ;-)

abstract class BaseControl extends Nette\Application\UI\Control
{
	// https://github.com/Kdyby/Autowired/blob/master/docs/en/index.md
	use Kdyby\Autowired\AutowireComponentFactories;

	protected function createComponentFlashes(IFlashesControlFactory $factory)
	{
		return $factory->create();
	}
}

class PollControl extends BaseControl
{
	private $polls;

	public function __construct(PollManager $polls)
	{
		parent::__construct();
		$this->polls = $polls;
	}
}

„Bez traitů“ není validní požadavek.

Btw, aby base control měl flashky mi přijde dost divný, všechny tvoje komponenty mají vždy flashky? Moje rozhodně ne. Navíc zobrazovat flashky na deseti místech v aplikaci je úchylnost která mate uživatele, daleko lepší je volat $this->presenter->flashMessage() a mít je všechny na jednom místě vykreslené někde v layoutu.

Oli
Člen | 1215
+
0
-

Tohle je taky brutální prasárna, komponenty nejsou služby!

Mohl by jsi mě vysvětlit proč je to prasárna? než jsem začal používat autowire komponent, tak jsem to používal v podstatě tak jak uvádím. Osobně nevidím zase až takovej rozdíl mezi tím, kdy si to injectnu do presenteru jako službu a kdy si tam injectnu tovarnu, ktera me tu sluzbu vytvori…

Teda, proc by neměli být komponenty injectovaný jako služby?

Filip Procházka
Moderator | 4668
+
0
-

Teda, proc by neměli být komponenty injectovaný jako služby?

V momentě kdy budeš chtít jednu komponentu dvakrát v tom samém presenteru nebo v jiné komponentě v jeden moment, tak ti to nebude fungovat, protože komponenta může být připojena pouze k jednomu rodiči a jenom jednou. Když ji zkusíš připojit dvakrát tak to vyhodí výjimku.

Proto je potřeba vytvářet nové instance komponent pro každé použití. Ideálně pomocí generovaných továrniček.

Zax
Člen | 370
+
0
-

@Filip Procházka: Principy DI jsem samozřejmě studoval. Mně stačí ±5 řádků abych si zapnul @inject, o kterém se ví, jak funguje (neboť v presenterech ho používají skoro všichni) a místo toho si mám do projektu přidat závislost na tvé knihovně a „magii“ schovat do traitu, který je právě v té knihovně. Někdo po mně kód převezme a ještě aby se začal učit tvou knihovnu. Navíc v dokumentaci uznáváš, že to není úplně čisté – což není ani inject. Nějak nechápu ten rozdíl… to si fakt radši na pět řádků prostě zapnu inject a je to všechno naprosto jasné a pochopitelné, byť ne zapouzdřené.

Furt nechápu – když ano u presenterů, proč ne u komponent? Nějaký BasePresenter a BaseControl má v projektu prakticky každý. Pro BasePresentery máme workaround, proč bych tedy neměl úplně ten samý workaround použít i u komponent a místo toho bych to měl řešit ještě úplně nějak jinak? To jako jen tak pro jistotu aby aplikace byla co nejmíň srozumitelná kdyby někdo po mně kód převzal? Anotaci @inject porozumí každý, kdo někdy používal Nette, i opoždění lidé. Navíc to chci pro presentery a komponenty a toť vše, nikde jinde to nepoužívám. Je to fakt až tak velký „zločin“? Fakt jedno velké WTF.

EDIT: Ano, potřebuji flasky u komponent, moje komponenty (hlavně právě ty backendové, kde pracuješ s různými seznamy a formuláři) jsou velmi často AJAXové a chci zobrazit hlášku přímo před očima uživatele, ne ji schovávat někam do hlavičky když má uživatel odscrollováno do půlky. A manipulovat mu přímo se scrollbarem by byla maximální úchylnost (IMHO). EDIT2: Jo a v případě že má uživatel vyplý JS, tak ho redirectnu na anchor, to jen tak pro jistotu ;-)

Editoval Zax (3. 6. 2014 19:56)

Filip Procházka
Moderator | 4668
+
0
-

Pro BasePresentery máme workaround, proč bych tedy neměl úplně ten samý workaround použít i u komponent a místo toho bych to měl řešit ještě úplně nějak jinak?

Jenže ty se na to díváš špatně a ptáš se špatně. Správná otázka je „a proč je to dovoleno v presenterech?“ né „proč to nemůžu používat i jinde?“. Na to je pak jasná odpověď, protože property injection v PHP je ten nejproblematičtější typ injekce a nejčistější je konstruktor.

Ještě bych i pochopil, kdybys měl jednu inject metodu (nebo klidně i ty properties) v BaseControlu. To je přijatelný workaround pro usnadnění problému.

Stále to ale není argument pro použití @injectkonkrétních komponentách. Tam bys měl striktně dodržovat konstruktor injection.

Nebaví mě opakovat pořád to samé dokola, poslal jsem ti 3 odkazy, tam je vše vysvětlené, pokud to nechápeš nebo odmítáš chápat tak tvoje smůla, já tě za každou cenu přemlouvat nebudu. Sám si to dělej jak chceš, ale nenabádej jiné programátory k prasárnám.

EDIT: Ano, potřebuji flasky u komponent, moje komponenty jsou velmi často AJAXové a chci zobrazit hlášku přímo před očima uživatele, ne ji schovávat někam do hlavičky když má uživatel odscrollováno do půlky.

Pokud máš složitější strukturu stránky pak to asi smysl dává. Stejně ale nedává smysl mít to úplně všude.

A víš, že v jeden ajaxový request můžeš invalidovat zároveň víc než jednu komponentu? Není problém invalidovat komponentu a zároveň i flashky v presenteru ;)

Oli
Člen | 1215
+
0
-

Filip Procházka napsal(a):

V momentě kdy budeš chtít jednu komponentu dvakrát v tom samém presenteru nebo v jiné komponentě v jeden moment, tak ti to nebude fungovat, protože komponenta může být připojena pouze k jednomu rodiči a jenom jednou. Když ji zkusíš připojit dvakrát tak to vyhodí výjimku.

Proto je potřeba vytvářet nové instance komponent pro každé použití. Ideálně pomocí generovaných továrniček.

Filip Procházka: jasně, tohle je regulérní důvod pro použití generované továrničky. To chápu a beru. Chtěl jsem dokonce i napsat do toho předchozího příspěvku, že tohle omezení beru na zřetel… Často ta moje komponenta jen obalovala formulář abych měl čistej presenter. Takovej formulář jsem věděl, že bude na stránce vždy max 1×.

Za takového předpokladu to pořád považuješ za brutální prasárnu? Je tam ještě nějakej zádrhel, kterej nevidím?

Editoval Oli (3. 6. 2014 20:08)

Filip Procházka
Moderator | 4668
+
0
-

Oli napsal(a):

Často ta moje komponenta jen obalovala formulář abych měl čistej presenter. Takovej formulář jsem věděl, že bude na stránce vždy max 1×. Za takového předpokladu to pořád považuješ za brutální prasárnu? Je tam ještě nějakej zádrhel, kterej nevidím?

Komponenta prostě není služba. Zatím se mi nikdy nevyplatilo napsat méně kódu za cenu ústupku z čistoty. Vždy jsem se na to buď já nebo kolega nachytal protože se to chovalo nestandardně.

Nejlepší co můžeš udělat je, říct si nějaký standard jaký budete v projektu dodržovat a ten mít prostě v celém projektu. Dělat v tomhle případu věci tak a v tomhle jinak, když je to možnost dělat jedním způsobem (bez nějakých návrhových ústupků), je vždycky horší.

Oli
Člen | 1215
+
+1
-

Filip Procházka: Dobře, díky. Beru to teda tak, že to není úplně čistý, ale když se to tak vezme kolem a kolem, tak to vlastně není ni proti ničemu. Jestliže víš o omezení který z toho plynou.

Nicméně právě díky autowire komponent už pomalu všechny komponenty přepisuju, tak aby byly generovaný továrničkou. Strašně návyková věc. Měl by jsi napsat upozornění ;-)

Zax
Člen | 370
+
0
-

Filip Procházka napsal(a):

Jenže ty se na to díváš špatně a ptáš se špatně. Správná otázka je „a proč je to dovoleno v presenterech?“ né „proč to nemůžu používat i jinde?“. Na to je pak jasná odpověď, protože property injection v PHP je ten nejproblematičtější typ injekce a nejčistější je konstruktor.

Ještě bych i pochopil, kdybys měl jednu inject metodu (nebo klidně i ty properties) v BaseControlu. To je přijatelný workaround pro usnadnění problému.

Stále to ale není argument pro použití @injectkonkrétních komponentách. Tam bys měl striktně dodržovat konstruktor injection.

No hurá, už se někam dostáváme! Ono to v konkrétních komponentách skutečně není třeba, ale je právě problém udržet konstruktor v BaseControl čistý a prostě lepší řešení než inject metody či anotace neznám (pokud si nechci přidávat další závislosti do projektu kvůli něčemu tak triviálnímu). A já preferuji anotace čistě kvůli konzistenci – v BasePresenteru @inject, v BaseControlu metoda inject* by mi prostě přišlo takový divný. Každopádně se bez nějakých kompromisů neobejdu. Workaroundy jsou naprosto zjevně nutné, proto nechápu proč na mě tak útočíš.

BTW traity nemají konstruktor, že? Co když chci na flashky místo BaseControl použít Trait? Jak do traitu dostanu závislost? Dost blbě, co? Potřebuji nějakou magii, která mi to tam dostane. Opět se přímo nabízí (@)inject(*).

Pokud máš složitější strukturu stránky pak to asi smysl dává. Stejně ale nedává smysl mít to úplně všude.

A víš že v jeden ajaxový request můžeš invalidovat zároveň víc než jednu komponentu? Není problém invalidovat komponentu a zároveň i flashky v presenteru ;)

Jednoduché stránky se časem mohou přetvořit ve složité. Radši to udělám už od základu jako pro složitou strukturu. Komponenty mám rád a hodlám je používat vždy a všude. Ajax v Nette funguje velmi pěkně, proto ho hodlám používat všude, kde se to hodí.

A ano, vím že se dá invalidovat víc komponent. Mám i některé komponenty, které používají $presenter->flashMessage(), ale třeba pro Ajax inline editaci někde uprostřed stránky to je nevhodné, spíš je lepší zobrazit flashku přesně tam, kde editace proběhla a nevynucovat scrollování na začátek stránky jenom kvůli tomu, abych uživateli oznámil, že právě provedl úpravu položky. Nebo když Ajaxově smaže nějakou položku, tak mu zobrazím flashku přímo na místě, kde ta položka byla (třeba rovnou i s tlačítkem „vzít zpět“) A jak říkám, podobných komponent mám celkem dost. Líbí se mi, že to tak může bez problémů fungovat, připadá mi to user-friendly, Ajax je sexy…

Editoval Zax (3. 6. 2014 20:29)

Filip Procházka
Moderator | 4668
+
0
-

Zax napsal(a):

Ono to v konkrétních komponentách skutečně není třeba, ale je právě problém udržet konstruktor v BaseControl čistý .

Jenže ono to celou dobu vypadá že se bavíš obecně o komponentách :) Pokud to máš jenom a pouze v té BaseControl a v potomcích používáš konstruktor injection tak je to stejný případ jako u BasePresenteru a tedy by to nemělo ničemu vadit.

a prostě lepší řešení než inject metody či anotace neznám (pokud si nechci přidávat další závislosti do projektu kvůli něčemu tak triviálnímu)

Autowired ten problém řeší jenom částěčně, usnadňuje ti psaní továrniček na komponenty v presenterech a komponentách. V momentě kdy potřebuješ službu tak už ti nepomůže. Protože jsi chtěl příklad s komponentou, logicky jsem použil rozšíření které celou situaci usnadňuje.

Mimochodem, když po tobě někdo přebere projekt a bude v něm hromada obskurdit, tak se mu bude učit hůře, než když v něm budou použity knihovny které zná :) Bránit se závislostem na cizích knihovnách je tedy dost kontraproduktivní, nehledě na to že psát si všechno sám je pro klienta a projekt dražší než když použiješ hotové řešení. Tady pozor! Nesnažím se ti vnutit své rozšíření, ale pouze obhajuji použití knihoven v projektu.

A já preferuji anotace čistě kvůli konzistenci – v BasePresenteru @inject, v BaseControlu metoda inject* by mi prostě přišlo takový divný.

to už je detail

Workaroundy jsou naprosto zjevně nutné, proto nechápu proč na mě tak útočíš.

Neber to prosím jako útok, ale jako normální diskuzi. Snažím se jenom předcházet tomu, aby se začátečníci kteří si tohle vlákno přečtou, učili cokoliv jiného než aktuální best-practices. Je strašně těžké takové lidi pak přeučit a ještě horší po nich přebírat projekty :)

Zax
Člen | 370
+
0
-

@Filip Procházka: No popravdě to byla moje chyba, nespecifikoval jsem to a s hanbou přiznávám, že píšu @inject i v konkrétních komponentách, ale činím tak jen ve svých projektech – šetří mi to čas a když není zapouzdřený BaseControl, tak je automaticky porušená zapouzdřenost i v potomcích, takže je to ve výsledku skoro jedno a můžu si klidně ušetřit psaní konstruktoru.

V těch, u kterých hrozí, že kód někdo převezme, používám konstruktor i v base komponentách (právě protože to chci psát co nejčistěji) a je děsnej opruz s tím pracovat – v polovině případů zapomenu zavolat parent::__construct a místo toho prostě znovu nadefinuju stejné properties a vložím je úplně stejně, jako kdyby v BaseControl vůbec nebyly. Což samozřejmě taky sucks a je v tom bordel. Proto si myslím, že prasárna je tak nějak na místě, pokud to vyřeší jinou prasárnu, a jsem rád, že mi dáváš tak nějak za pravdu v případě BaseControl ;-)

Nicméně zrovna tohle (@inject v komponentách) by mě osobně v převzatém projektu trápilo asi ze všeho nejméně, protože bych hnedka pochopil, která bije. A obecně bych ani do public properties nic nezkoušel zapisovat. Každá nastavitelná vlastnost by vždy měla mít setter (nejlépe vracející $this pro fluent cukřík), nezávisle na viditelnosti. Pokud vlastnost nemá setter, automaticky ji považuji za read-only.

No jo, asi si do podpisu přidám upozornění ať mě nikdo nebere vážně :-D Preferuji totiž účelnost, produktivitu a jednoduchost před akademickou čistotou.

Omlouvám se, jestli jsem se nějak dotkl tvé filozofie.

Filip Procházka
Moderator | 4668
+
0
-

No jo, asi si do podpisu přidám upozornění ať mě nikdo nebere vážně :-D Preferuji totiž účelnost, produktivitu a jednoduchost před akademickou čistotou.

To si asi nerozumíme :) Já taky budu mít raději hotovou aplikaci, než abych měsíc maturoval nad čistotou zápisu několika tříd, všiml sis že jsem napsal a používám kdyby/autowired? :) Když vím jak věc napsat čistě, tak ji napíšu čistě, když to nevím tak ji raději naprototypuju jako bastlík a pak to někdy třeba přepíšu než abych to neměl vůbec.

Omlouvám se, jestli jsem se nějak dotkl tvé filozofie.

Nejde o mou filozofii, ale o filozofii frameworku. Nette má určité best-practices, které se snaží své uživatele učit. Jelikož se považuju za evangelizátora Nette, tak je prostě učím i já (mimo jiné taky proto, že jim taky věřím).

Šaman
Člen | 2666
+
0
-

Filip Procházka napsal(a):
Ještě bych i pochopil, kdybys měl jednu inject metodu (nebo klidně i ty properties) v BaseControlu. To je přijatelný workaround pro usnadnění problému.

Nejde o mou filozofii, ale o filozofii frameworku. Nette má určité best-practices, které se snaží své uživatele učit. Jelikož se považuju za evangelizátora Nette, tak je prostě učím i já (mimo jiné taky proto, že jim taky věřím).

Tohle téma se tu poslední dobou opakuje (a tentokrát jsem to nebyl já, kdo ho nadnesl:), takže ještě jednou a snad naposledy apeluji na možnost nějak Nette-way zapnou inject metody i pro komponenty. V tuhle chvíli inject metoda v BaseControl vyžaduje, aby se zapsal přepínač autowired: yes ke každému potomkovi. A jak je vidět, mnoho lidí to obchází. Kdo nechce, ten to používat nebude, ostatní to aspoň nebudou muset hackovat.
Naopak anotaci @inject bych klidně zrušil úplně – kdo o ni má zájem, může použit Kdyby. Z důvodu zpětné kompatibility se asi nezruší, ale rozhodně bych ji ani v presenterech neuváděl jako best-practise (v příkladech a v sandboxu), ale spíš jako syntactic-sugar. Nováčci si jinak zvyknou na nevhodný způsob (který pak vyžadují všude) a kritikům to jen dodává munici.
Jo píšu to sem proto, že na poslední sobotu se nedostanu a jak je i z tohoto vlákna vidět, neškodilo by to ještě jednou probrat v nějaké širší komunitě.

Mě osobně by se to nejvíce líbilo takto:

Konstruktor Inject metoda Anotace @inject
Abstraktní presenter Ano Doporučeno Nedoporučeno (porušuje zapouzdření)
Konkrétní presenter Doporučeno Ano Nedoporučeno
Abstraktní komponenta Ano Doporučeno(?) Ne
Konkrétní komponenta Doporučeno Ano Ne
Třída modelu Doporučeno Ne (ano jen v případě autowire: yes) Ne
Zax
Člen | 370
+
0
-

Šaman napsal(a):

Tohle téma se tu poslední dobou opakuje (a tentokrát jsem to nebyl já, kdo ho nadnesl:)

Však je to jedině dobře, mě osobně třeba tohle téma hodně zajímá, protože hodně využívám abstraktní komponenty a potřebuji do nich dostávat závislosti. Celkově mi přijde, že komponenty jsou hodně opomíjené a takové jakoby „chudé“ oproti presenterům ve spoustě věcí. A skoro mám pocit, že tenhle problém tu řeším akorát já a pár dalších jedinců – to skoro nikdo komponenty nepoužívá? :-(

takže ještě jednou a snad naposledy apeluji na možnost nějak Nette-way zapnou inject metody i pro komponenty. V tuhle chvíli inject metoda v BaseControl vyžaduje, aby se zapsal přepínač autowired: yes ke každému potomkovi. A jak je vidět, mnoho lidí to obchází. Kdo nechce, ten to používat nebude, ostatní to aspoň nebudou muset hackovat.

+1 pro Nette-way jak toto řešit. Beru cokoliv. Tedy cokoliv, kromě nutnosti používat přepínače v configu (zkrácený zápis „- Namespace\IFactory“ je prostě boží).

Naopak anotaci @inject bych klidně zrušil úplně – kdo o ni má zájem, může použit Kdyby. Z důvodu zpětné kompatibility se asi nezruší, ale rozhodně bych ji ani v presenterech neuváděl jako best-practise (v příkladech a v sandboxu), ale spíš jako syntactic-sugar. Nováčci si jinak zvyknou na nevhodný způsob (který pak vyžadují všude) a kritikům to jen dodává munici.
Jo píšu to sem proto, že na poslední sobotu se nedostanu a jak je i z tohoto vlákna vidět, neškodilo by to ještě jednou probrat v nějaké širší komunitě.

Mě osobně by se to nejvíce líbilo takto:

Konstruktor Inject metoda Anotace @inject
Abstraktní presenter Ano Doporučeno Nedoporučeno (porušuje zapouzdření)
Konkrétní presenter Doporučeno Ano Nedoporučeno
Abstraktní komponenta Ano Doporučeno(?) Ne
Konkrétní komponenta Doporučeno Ano Ne
Třída modelu Doporučeno Ne (ano jen v případě autowire: yes) Ne

Takto bych to klidně taky bral. Má to opodstatnění a určitou logiku. Naopak mě dost žralo, že @inject je u presenterů doporučený způsob a u komponent prasárna – je to prostě rozpor jak prase. Jediný rozdíl mezi nimi je, že presenter může obsahovat komponenty, ale naopak to nejde, což IMHO by obzvlášť mělo znamenat, že @inject u presenterů je fuj a u komponent trochu míň fuj – když přepíšu službu v presenteru, jde do háje celý presenter se všemi komponentami, které obsahuje. Když přepíšu službu v komponentě, spadne mi jenom ta komponenta (a vše co se vykresluje po ní).

Nicméně bych @inject nerušil úplně, naopak bych to povolil i u komponent, je to jak říkáš syntax sugar a hodí se to, když je potřeba něco rychle udělat a nezáleží tolik na čistotě. Nebo třeba něco vyvíjíš a nemáš ještě úplně jasné všechny závislosti, tak použiješ nejdřív anotace, protože jejich nesporná výhoda oproti ostatním řešením je ta, že se velice dobře udržují (přidání nebo odebrání závislosti změní dva řádky na stejném místě, nikoliv 4 řádky na 3 různých místech), a když máš komponentu hotovou, tak to během chvilky přepíšeš do inject metody (nebo do konstruktoru). Třeba by jako kompromis mohlo jít povolit je jenom v development režimu?

A předpokládám, že best practice pro inject metody je, aby vyhazovaly výjimku, pokud už služby existují, je to tak správně? Nebo je to overkill?

Editoval Zax (4. 6. 2014 2:53)

Filip Procházka
Moderator | 4668
+
0
-

Zax napsal(a):

A skoro mám pocit, že tenhle problém tu řeším akorát já a pár dalších jedinců – to skoro nikdo komponenty nepoužívá?

Používám je úplně na všechno a zatím jsem s tímhle problém nikdy neměl :)

Nebo třeba něco vyvíjíš a nemáš ještě úplně jasné všechny závislosti, tak použiješ nejdřív anotace

Ty nepoužíváš IDE? Přidat a odebrat závislost je otázka několika stisknutí kláves a s nativním autowire v nette/di je změna závislostí úplná lahoda, protože ani nemusíš sahat na config.

protože jejich nesporná výhoda oproti ostatním řešením je ta, že se velice dobře udržují (přidání nebo odebrání závislosti změní dva řádky na stejném místě, nikoliv 4 řádky na 3 různých místech)

Tohle tvrzení je ekvivalentní tomu, jako bys řekl, že statický přístup je lepší, protože je s tím méně psaní.

Šaman napsal(a):

Tohle téma se tu poslední dobou opakuje (a tentokrát jsem to nebyl já, kdo ho nadnesl:), takže ještě jednou a snad naposledy apeluji na možnost nějak Nette-way zapnou inject metody i pro komponenty.

V momentě kdy tohle bude zaplé by default, tak to lidé prostě budou používat na všechno. Když se bez toho nezvládneš obejít, tak napsat compiler extension, který zapne injecty pro všechny komponenty, je práce na 5 minut a kód na 10 řádků.

Casper
Člen | 253
+
0
-

@Filip Procházka

A vůbec, správně to tady má jenom matej21, byť možná zbytečně složitě, entitu bych předával raději setterem (má to víc výhod než jenom jednodušší konfiguraci).

Můžeš prosím upřesnit ty výhody setteru?

Zax
Člen | 370
+
0
-

Filip Procházka napsal(a):

Zax napsal(a):

A skoro mám pocit, že tenhle problém tu řeším akorát já a pár dalších jedinců – to skoro nikdo komponenty nepoužívá?

Používám je úplně na všechno a zatím jsem s tímhle problém nikdy neměl :)

Nemám páru jak to děláš. Taky používám komponenty na všechno a krom toho, že mi v základu chybí pár fičur (views, metoda forward), mám i potřebu používat Base komponenty i na různé další věci. Zatím to u mě vypadá zhruba takto:

  • BaseControl: „views“ (pokud se zavolá signál, hledá se šablona se stejným názvem, pokud neexistuje nebo se signál nevolá, hledá se default.latte), metoda forward, metoda redirectOrForward (abych nemusel dokola psát if($this->isAjax())…), metoda enableAjax (v šabloně na to musím myslet, ale nakonec je fajn mít někde po ruce možnost ajax zapnout/vypnout), obsahuje subkomponentu na flash messages, syntax sugar pro kontrolu oprávnění (anotace @resource Zdroj pro celou komponentu + metoda $this->isAllowed([$resource,]$privilege) + magické $this->canSomething, což se přeloží jako $this->isAllowed(‚Something‘) + secured signálům stačí anotace @privilege)
    • BaseCollectionControl: abstraktní metoda na získání kolekce a přetížitelná metoda na filtrování dat (v základu vrací celou kolekci a nic nefiltruje)
      • BasePaginableCollectionControl: subkomponenta paginator + přetížená metoda pro filtrování dat ⇒ paginator díky tomu funguje v podstatě automaticky
        • BaseEditableCollectionControl: automaticky přijímá signály add, edit a delete, kontroluje oprávnění, obsahuje některé základní abstraktní metody pro práci s entitami, v základu bych chtěl, aby to umělo i vygenerovat přidávací/editační formulář z definice entity, ale k tomu jsem se ještě nedostal – zatím tedy řeším formuláře přes abstraktní metody a musím si je u konkrétních komponent dopsat
          • BaseEditableCollectionFilesControl: obsahuje subkomponentu na správu souborů, abstraktní metody pro ukládání vazeb mezi entitou a soubory

a mohl bych pokračovat…

Závislosti jsou jasné:

  • BaseControl závisí na IFlashMessagesFactory
  • BasePaginableCollectionControl závisí na IPaginatorFactory
  • BaseEditableCollectionFilesControl závisí na IFileManagerFactory

Vztahy taky dávají smysl:

  • naprostá většina mých komponent chce kontrolovat oprávnění a zobrazovat vlastní flashMessages
  • ne každá kolekce vyžaduje stránkovadlo
  • naopak komponenta pro kolekce s možností úprav docela určitě bude vyžadovat i stránkovadlo
  • k čemu je upload souborů u kolekcí (třeba článků) je snad dost self-explanatory

Všechny tyto komponenty mají svůj validní use-case:

  • chci zobrazit jeden článek: použiju BaseControl, vytáhnu článek, kouknu jestli je public, pokud není, zkontroluju oprávnění (třeba pomocí $this->canEdit).
  • chci na hlavní straně zobrazit pět nejnovějších témat na fóru: použiju BaseCollectionControl a napíšu metodu na získávání dat z modelu. Limit nastavím v přetížené metodě pro filtrování (někdy se hodí třeba zjistit počet všech položek před aplikováním filtru, proto to mám takto oddělené). Pokud bych chtěl víc položek se stránkováním, použiju BasePaginableCollectionControl
  • chci zobrazit administrační rozhraní pro správu uživatelů: použiju BaseEditableCollectionControl a dopíšu nezbytné abstraktní metody.
  • chci zobrazit seznam článků, každý článek může mít k sobě připojené libovolné množství souborů, a administrátorovi chci dát možnost spravovat vše přímo na stránce: použiju BaseEditableCollectionFilesControl, dopíšu pár abstraktních metod a nastavím hlavní adresář a povolené typy souborů.

Celé je to hodně založené na DRY a celkem dobře mi to funguje (tedy pokud nejsem nucen všude používat konstruktor, pak jsem naopak docela v pr.), řeším tím spoustu častých problémů. Pokud to dělám komplet špatně, rád se přiučím.

>

Nebo třeba něco vyvíjíš a nemáš ještě úplně jasné všechny závislosti, tak použiješ nejdřív anotace

Ty nepoužíváš IDE? Přidat a odebrat závislost je otázka několika stisknutí kláves a s nativním autowire v nette/di je změna závislostí úplná lahoda, protože ani nemusíš sahat na config.

Že většina IDE umí vygenerovat settery je samozřejmé. Ukaž mi ale IDE, které vezme inject metodu, rozparsuje ji na jednotlivé kousíčky (závislosti) a dá ti možnost stisknutím několika kláves odebrat nějakou závislost. V NetBeans jsem tuto funkci nenašel :-P Vlastnost s anotací smáznu hádej za jak dlouho.

protože jejich nesporná výhoda oproti ostatním řešením je ta, že se velice dobře udržují (přidání nebo odebrání závislosti změní dva řádky na stejném místě, nikoliv 4 řádky na 3 různých místech)

Tohle tvrzení je ekvivalentní tomu, jako bys řekl, že statický přístup je lepší, protože je s tím méně psaní.

Prdlajs. Smazat dva řádky (property a anotace) na jednom místě zabere cca dvě vteřiny i s hledáním, kde přesně mám mazat. Smazat tyto dva stejné řádky a k tomu ještě další dva řádky někde v inject metodě (jeden argument a jedno přiřazení) prostě trvá dýl, říkej si co chceš. Srovnání se statickým přístupem je úplně totálně mimo – statickou třídu do normální třídy budu přepisovat dost blbě, bude to trvat, hodně se přitom sblížím s Tracy a neusnadní mi to vůbec nic, oproti tomu transformace @inject ⇒ metoda inject zabere skutečně jen okamžik a na chodu aplikace se nezmění absolutně nic. A tou anotací si aspoň usnadním bouřlivý vývoj, kdy zběsile přidávám a odebírám závislosti a fakt nechci zrovna řešit, jestli je to čisté, to se dá snadno vyřešit ve chvíli, kdy mám jasně dané všechny závislosti.

Nicméně musím diskutujícím poděkovat (hlavně Filipu Procházkovi), jsou to celkem rady nad zlato!

Editoval Zax (4. 6. 2014 9:16)

Filip Procházka
Moderator | 4668
+
0
-

Myslím že to neděláš úplně špatně, máš to celkem pěkně vymyšlené, ale nevím jestli je dobrý takhle hrotit tu dědičnost. Věřím, že by to šlo navrhnout i pomocí kompozice takovým způsobem, aby tvůj BaseControl měl jenom nějaké základní vylepšeníčka a ty sis v potomcích vždy poskládal do sebe co potřebuješ.

Takhle teď vypadá můj BaseControl, je to imho slušný kompromis mezi pragmatismem a čistotou, protože když budu chtít tak si nakonfiguruju injecty, když nebudu chtít, tak si translator najde sama. Ovšem uznávám, že kdyby byly povolené @inject na komponentách, tak bych nejspíš ten translator měl řešený přes ně.

Na druhou stranu si nemyslím, že je v BaseControlu potřeba přidávat cokoliv dalšího, i to co tam mám teď by šlo pravděpodobně dost ořezat a věci typu onAttached by klidně mohly být přímo v Nette. Od 2.2 půjdou vyhodit i ty helpery (tohle je psané pro 2.1) a zbude mi tak akorát traita na AutowireComponentFactories a translator.

Mám rozdělanou dost našlapanou administraci pro Doctrine, která je celá založená na komponentách, tak uvidíme jak se s tím zvládnu poprat a pak si to můžeme třeba poměřit znova, až to bude aktuální :)

Prdlajs. Smazat dva řádky (property a anotace) na jednom místě zabere cca dvě vteřiny i s hledáním, kde přesně mám mazat. Smazat tyto dva stejné řádky a k tomu ještě další dva řádky někde v inject metodě (jeden argument a jedno přiřazení) prostě trvá dýl, říkej si co chceš.

Já se ale bavím o konstruktoru (ikdyž to jde aplikovat i na inject metody). Jistěže to trvá déle, ale můj argument platí :)

A teď mi řekni jestli to ty i všichni tví kolegové takto poctivě děláte. To je stejné jako když něco naprogramuješ a řekneš to šéfovi, ten po tobě chce ať to okamžitě deployneš a ty si řekneš však co, deploynu to a testy napíšu potom. Nevěřím, že vám to v praxi dlouhodobě funguje. Pokud to neuděláš hned dobře, tak prostě není motivace se pak k tomu vracet, navíc když je spousta dalších věcí co je potřeba udělat.

Mimochodem, vsadil bych se, že hádáním se mnou jsi ztratil mnohem více času, než kdy ušetříš tím, že bys místo @inject metod psal rovnou konstruktor injection :)


Každopádně mi přijde, že se začínáme dost odchylovat od tématu.

Filip Procházka
Moderator | 4668
+
0
-

Casper napsal(a):

Můžeš prosím upřesnit ty výhody setteru?

Nette když se například kanonizuje, tak musí nejprve posbírat parametry z presenteru (a komponent), tedy jsem schopnej poskládat url, která například předává parametr tvé komponentě a tedy ji tím zinicializuji a když máš blbě ošetřenej životní cyklus v presenteru, tak místo 404 (protože například chci nějaký článek který neexistuje) ti vygeneruju 500.

Je tedy podle mě lepší používat setter a v vždy se v komponentě spoléhat na to, že ta entita tam nemusí být. Sice můžeš dát i = NULL do konstruktoru, aby neumřel když tam nepředáš objekt, ale tady dává setter lepší smysl, protože settery jsou na závislosti bez kterých objekt může existovat.

Nehledě na to, že konfigurace se smrskne z tohohle

services:
    -
        class: SomeArticleControl
        implement: ISomeArticleControlFactory
        parameters: [Article $article]
        arguments: [%article%]

na tohle

services:
    - ISomeArticleControlFactory

(Už aby se dořešilo tohle https://github.com/nette/di/pull/4 :)

Použití se pak změní z

protected function createComponentArticle(ISomeArticleControlFactory $factory)
{
	return $factory->create($this->article);
}

na

protected function createComponentArticle(ISomeArticleControlFactory $factory)
{
	return $factory->create()->setArticle($this->article);
}

Což IMHO není taková hrůza :)

Šaman
Člen | 2666
+
0
-

Souhlasím se Zaxem v jedné klíčové věci – Nette framework je aktuálně nekonzistentní, protože přístup, který je u jednoho typu tříd doporučován(!) je jinde zavržen, že není čistý. Nalejme si čistého vína – není to čisté ani v presenterech, jediný důvod existence anotace @inject je lenost. Kdyby programátor nebyl líný, tak všude bude používat inject metody, které mají úplně přesně stejný use case jako property injection, ale neporušuji zapouzdření.

Tak proč, @#$$%!, se tento přístup jinde zakazuje? Argument, že anotace @inject nejsou potřeba je sice pravda, ale zároveň je to argumentační faul. Ony totiž nejsou potřeba ani u presenterů, přesto tam jsou.

A proč, @#$$%! *&^$%#, u ostatních tříd nejdou používat ani čistější inject metody? Kde je v nějaké učebnici OOP/MVC napsané, že jediné místo, kde hrozí construction hell jsou presentery? Jediný argument proti inject metodám je ten, že neznalý programátor a ignoranti to budoupoužívat i tam, kde to není potřeba. Nezlobte se na mě, jsem přesvědčený, že framework k produkčnímu nasazení má dávat nástroje k efektivnímu vývoji a nebýt školním systémem, který bude hlídat, jestli nedělám činnost, která by mohla(!) být prasením.

Po Zaxově argumentech (hlavně rychlejší vývoj) bych tu tabulku upravil – myslím, že všechny tři způsoby by měly být povolené vždy. Doporučený způsob všech povinných závislostí konkrétních tříd konstruktor, jen v abstraktních třídách by to byl konstruktor i inject metody. Anotace @inject by měla být všude nedoporučená a neměla by se vyskytovat v oficiálních ukázkách, quickstartu a sandboxu. Umožňuje ale velmi efektivní návrh kostry aplikace, jen by po ni měl přijít refaktoring a uhlazení. Koneckonců u tříd, které mi řeší nějaký algoritmus, taky na prvotní návrh používám public property a až po odladění to přepisuji na settery a gettery.

Filip Procházka
Moderator | 4668
+
0
-

Vždyť to tak je, všude se doporučuje používat konstruktor injection, ale v presenterech si to můžeš ulehčit pomocí @inject nebo inject*(). I v quickstartu je dokonce v presenterech konstruktor injection :)

Nette ti v ničem nebrání, jenom tě v některých věcech zkrátka nebude by default podporovat.

Nemá smysl tady tady dál spamovat vyřešené téma, založ RFC a můžeme se pobavit nad ním.

Šaman
Člen | 2666
+
0
-

Ajo, sorry, nějak jsem se už delší dobu nepodíval na quickstart. Opravdu se i do presenterů injectuje přes konstruktor. V TodoListu to tak nebylo, ale ten už je rok pryč a ten Blog to má od začátku…