Jak správně vytvářet Componenty přes context?
- joseff
- Člen | 233
Stále mi moc nedochazí, jak mohu správně vytvořit componentu přez di. Základem je to, že chci vytvořit novou instanci nějaké třídy dedící z \Nette\Application\UI\Control a předat ji do konstruktoru jako první parametr presenter (to je pokud se nemýlím nutné ke správnému fungování komponenty). Jenže jak mohu předat presenter, když mi ji vytváří factory? Parametr do factory strčit nejde, nebo ano? Zůstávám tedy u toho, že si vytvářím komponentu v Presenteru slovem new NazevKomponenty, což je dle mého názoru taky špatně. Jak je to tedy správně? Díky
- Vojtěch Dobeš
- Gold Partner | 1316
Ono s tím předáním presenteru je to takto: jakmile továrnička
(protected function createComponentFoo...
) vrátí instanci
komponenty, tak se na jejím parent prvku (instanci IContainer
,
což jsou kupříkladu presentery) zavolá
->addComponent($component, $name)
– kde
$component
je právě instance té mojí komponenty. Tam se provede
předání presenteru komponentě. Děje se tak tedy automaticky po vykonání
továrničky. Viz zdroják
Stručně řečeno: v současně době komponentám není potřeba
předávat v konstruktoru presenter ani žádný jiný parent prvek (prostě
$this
).
protected function createComponentFoo()
{
return new FooControl;
}
Tohle je naprosto OK továrnička a FooControl
bude mít
k dispozici presenter (ne ale pochopitelně v konstruktoru, ten se volá
(zcela logicky a očividně :) ) v továrničce – připojení k presenteru
se odehraje až po jejím vykonání.
Je to dostatečně srozumitelné?
Update: ještě stojí za doplnění, že připojení
k presenteru lze přímo detekovat pomocí páru metod monitor
a
attached
. Tu první ani není třeba volat, jistojistý okamžik
připojení k presenteru pak lze odchytit v attached
:
protected function attached($parent)
{
parent::attached($parent);
if ($parent instanceof Nette\Application\UI\Presenter) {
// do staff
$this->presenter === $parent; // platí
}
}
Editoval vojtech.dobes (16. 3. 2012 5:21)
- joseff
- Člen | 233
Díky za vysvětlení, ale já bych se právě raději vyhnul tomu slovu new ve vytváření Komponenty a raději bych komponenty vytvářel z contextu továrničkou, tedy
protected function createComponentFoo()
{
return $this->context->createFooControl();
}
Což mi přijde mnohem čistější řešení, ale pak se presenter nepřipojí. Když pak tedy v komponentě zavolám $this->link(…) zařve to, že není připojený presenter. Takže teda jak?
Editoval joseff (16. 3. 2012 9:40)
- Tharos
- Člen | 1030
joseff napsal(a):
Což mi přijde mnohem čistější řešení, ale…
Moc by mě zajímala Tvá odpověď na mé následující otázky, ale než je položím… Neber je prosím jako nějaký útok nebo snahu o flame! Ber to třeba jen jako můj soukromý průzkum. :)
V čem ti tohle přijde mnohem čistější? Jaké to má pro Tebe
praktické výhody oproti volání new
? Je něco nečistého na
konstrukci new
?
Nesnažím se vůbec říct, že by mi Tvé řešení přišlo „špatné“ nebo tak něco. Určitě mají oba přístupy své výhody a nevýhody (výhodou je určitě možnost autowiringu) a opravdu by mě jen zajímalo, jaké má to či ono řešení výhody/nevýhody přímo pro Tebe a z čeho plyne to označení čisté/nečisté. Klidně ale můj post ignoruj. :) Díky!
- joseff
- Člen | 233
Vpohodě, nejsem taková citlivka :-)
Většinou do komponenty vkládám ještě mnoho dalších závislostí, např.
translator, wwwDir, basePath, cache atd… a prostě mi přijde čistější
napsat do configu:
class: ComponentFoo
arguments: [%wwwDir%, @translator, %basePath%]
než toto:
protected function createComponentFoo()
{
return $this->context->createFooControl($this->context->parameters['wwwDir'], $this->context->translator, $this->context->parameters['basePath']);
}
Co když tuto komponentu vytvářím na více místech a v budoucnu se rozhodnu že tam přidám cache? Pokud to mám vše v configu jen přidám do configu do arguments cache a trochu překopu samotnou komponentu. Ale pokud to budu vždy předávat v prezenteru budu muset najít všechny výskyty createComponentComponentFoo a všude to přepsat. Nehledě na to že samotný zápis v presenteru je dost dlouhý a budu ho stále opakovat (kopírovat). No a nakonec bych chtěl mít všechny závislosti pěkně pohromadě. Pokud někde píšu blbost, budu rád když mi to někdo vysvětlí…
Editoval joseff (16. 3. 2012 10:42)
- Tharos
- Člen | 1030
Super, díky! Máš to dobře promyšlené a dává to smysl :). To opakované vytváření by se asi dalo vyřešit i vyčleněním té továrničky na úroveň nějakého společného předka, ale to se možná shodneme, že je méně elegantní řešení (minimálně pro mě by pak bylo hůře čitelné, kde že se ta komponenta vlastně bere).
- joseff
- Člen | 233
Mě by asi nejvíce uspokojilo, kdyby se do továrničky dal předat parametr, tedy
protected function createComponentFoo()
{
return $this->context->createFooControl($this);
}
a továnička by pak dala $this (tedy presenter) do konstuktoru a ostatní zvyslosti za to, ale to nevím jak toho dosáhnout. Druhou možností je to udělat takto:
protected function createComponentFoo()
{
return $this->context->createFooControl()->setPresenter($this);
}
Ale žádnou metodu setPresenter jsem v controlu nenašel :-(
Výsledek je tedy že vlasně nevím jak to mám vytvářet…
- Filip Procházka
- Moderator | 4668
Něco ke čtení. A teď konkrétně, mělo by to jít nějak takto.
factories:
class: ComponentFoo
arguments: [%presenter%, %wwwDir%, @translator, %basePath%]
paremeters: [presenter]
Ale opět se musím vrátit na začátek tématu. Nemyslím si, že presenter během vytváření komponenty potřebuješ. Komponenta se totiž buď vykresluje, nebo zpracovává signál.
Když zpracovává signál, signál je volán presenterem. Je volán až
v momentě, když už je komponenta dávno připojena a má přístupný
$this->presenter
.
Když komponentu vykresluješ, děláš tak v šabloně a předtím než
komponentu vykreslíš, tak se musí vytvořit v továrničce, tedy opět
během zpracování metody render máš už $this->presenter
přístupný.
Pokud potřebuješ presenter dříve, což je oprávněný požadavek, přenes tuto logiku do metody attached, jak radí Vojta.
Tedy místo
public function __construct($parent, $name)
{
parent::__construct($parent, $name);
$this->presenter->link(); // což je stejně hovadina, proč bys tady potřeboval odkaz?
}
budeš psát
protected function attached($obj)
{
parent::attached($obj);
if (!$obj instanceof Nette\Application\UI\Presenter) {
return;
}
// sem se zpracování dostane ihned po připojení k presenteru
// zde máš už tedy presenter přístupný
$this->presenter->link();
}
Je to daleko čistější řešení a více
ComponentModel
-friendly. Něco
ke čtení.
- David Grudl
- Nette Core | 8171
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
- joseff
- Člen | 233
HosipLan napsal(a):
Když komponentu vykresluješ, děláš tak v šabloně a předtím než komponentu vykreslíš, tak se musí vytvořit v továrničce, tedy opět během zpracování metody render máš už
$this->presenter
přístupný.
Tak asi něco dělám blbě, ale tohle mi nefunguje:
V Prezenteru:
protected function createComponentFoo(){
return $this->context->fooControl;
}
V sablone controlu:
<a n:href=":Homepage:default">domu</a>
Chyba: Component or subcomponent name must not be empty string.
// nechapu jak to mam nastavit
Nebo toto:
<a href="{plink :Homepage:default}">domu</a>
Chyba: Call to a member function link() on a non-object
// Z cehoz podle me vyplyva ze tam presenter pripojeny neni
Pokud v sablone nepouzivam link, tak se vse vypise OK.
- Jendaaa
- Člen | 21
joseff napsal(a):
David Grudl napsal(a):
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
Ale pokud používám new FooControl v presenteru tak DI porušuji také nebo ne? Takže jak na to správně přes DI?
Správně by si asi presenter neměl sahat do contextu pro komponenty, ale měly by my být předány konstruktorem, popř. setterem…
Editoval Jendaaa (21. 3. 2012 9:23)
- ViPEr*CZ*
- Člen | 814
David Grudl napsal(a):
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
To je takhle špatně? Nebo jsem nerozuměl odpovědi?
public function createComponentAddressBookFormAddFull() {
$form = new MyForm($this->context->database);
return $form;
}
- llook
- Člen | 407
ViPErCZ napsal(a):
David Grudl napsal(a):
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
To je takhle špatně? Nebo jsem nerozuměl odpovědi?
public function createComponentAddressBookFormAddFull() { $form = new MyForm($this->context->database); return $form; }
To není špatně, ale také to není DI.
Ty ses ptal, jak to dělat správně podle DI a ten vzor popisuje řešení, kde objekt dostává všechny své závislosti zvenčí. Takže kdybys mermomocí chtěl použít DI, tak bys komponenty musel vytvářet někde „venku“ a do presenteru je injektovat. Na vytváření komponent se prostě DI nehodí.
- mkoubik
- Člen | 728
Z důvodu zpětné kompatibility je contejner naprosto legitimní závislost
presenteru. Jako kompromisní řešení se mi líbí si někam uložit třeba
$this->componentsFactory = $this->context->components
aby
továrničky nesahaly na celý context, ale jenom na tu část kterou
potřebujou. V budoucnu by se to dalo vyčlenit a předávat do presenteru
zvlášť.
- Ascaria
- Člen | 187
David Grudl napsal(a):
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
Ale jen v případě komponenty, v presenteru můžu používat kontext a zároveň neporušovat DI, nebo ne? Nenapadá mě způsob, jak bez použití contextu v presenteru předat komponentě nějaký model co se vytváří v neonu.
Jendaaa napsal(a):
joseff napsal(a):
David Grudl napsal(a):
Jen pro jistotu dodávám, že Dependency Injection je opakem contextu, tudíž používám-li context, porušuji a obcházím DI.
Ale pokud používám new FooControl v presenteru tak DI porušuji také nebo ne? Takže jak na to správně přes DI?
Správně by si asi presenter neměl sahat do contextu pro komponenty, ale měly by my být předány konstruktorem, popř. setterem…
Podle mě by presenter také neměl sahat pro komponenty do kontextu, měl by je vytvářet. Do kontextu patří modely a ne komponenty.
Editoval Ascaria (21. 3. 2012 19:55)
- Vojtěch Dobeš
- Gold Partner | 1316
Ascaria napsal(a):
Ale jen v případě komponenty, v presenteru můžu používat kontext a zároveň neporušovat DI, nebo ne? Nenapadá mě způsob, jak bez použití contextu v presenteru předat komponentě nějaký model co se vytváří v neonu.
Proti DI je právě $context
, tedy pattern
zvaný Service Locator. Stačí si položit otázku, zdali presenteru
předávám všechny závislosti. Odkud si jako bere ten model? Předávám ho
presenteru v konstruktoru nebo setteru? Ne – DI dostává na frak.
Ascaria napsal(a):
Podle mě by presenter také neměl sahat pro komponenty do kontextu, měl by je vytvářet. Do kontextu patří modely a ne komponenty.
Kontext není knihovnička pro modelovou část aplikace, je to DI kontejner. V ideálním světě by v presenteru být neměl, protože všechno se předá čistě konstruktorem nebo setterem, nikoliv odkudsi z klobouku :).