Na navigaci | Klávesové zkratky

DI a předávání závislostí presenterům

V Nette jsme narazili na zajímavý problém, který úzce souvisí s článkem Dependency Injection a předávání závislostí a komplikacemi způsobenými dědičností.

Stěžejním bodem tvorby aplikací v Nette je vytváření vlastních presenterů, tedy potomků třídy Nette\Application\UI\Presenter (dále Presenter s velkým P). Ta má své závislosti. Byl bych rád, aby uživatelé stavěli aplikace v souladu s DI a tedy se i jejich presentery hlásily ke svým závislostem.

Jenže jak to realizovat? Možná řešení:

  1. Presenter i potomek uvedou závislosti v konstruktoru – to by bylo peklo
  2. pro každou závislost se použije samostatný setter – zbytečně moc režijního kódu
  3. použije se jedna metoda pro závislosti Presenteru a jedna pro potomka

a) Presenter využije konstruktor, potomek metodu inject() b) potomek využije konstruktor, Presenter metodu inject() c) nebudeme do toho tahat konstruktor, použijí se jiné metody

  1. použije se třída PresenterDependencies sdružující závislosti do jedné proměnné
  2. injektování přímo do proměnných – nevím, jak pojmout

ad 1) Jak už jsem psal v odkazovaném článku, pokud by Presenter i jeho potomek pro předání závislostí použili konstruktor, musel by se ten překrývající postarat o předání všech rodičovských závislostí. Ale jakmile se rodičovské závislosti změní (refactoring Presenteru), všechny třídy se rozbijí. Neakceptovatelné.

ad 2) Zda předávat všechny závislosti jednou metodou nebo zda vytvořit metodu/injektor pro každou závislost zvlášť, je v principu jedno. Protože nemám rád, když je moc režijního kódu a zbytečně bobtná API, volím raději jednu metodu.

ad 3) Šikovně vypadají přístupy 3a a 3b, tedy použít konstruktor pro závislosti jedné strany a metodu inject() pro závislosti druhé. O tom, který způsob použít, jsme se přeli s Honzou Tichým. On preferoval způsob 3b s tím, že když už nemáme žádné ideální řešení, zvolme z možných to, které bude funkční, pokud uživatel začne věc řešit intuitivně, aniž by věděl o nějaké metodě inject(). A intuitivně použije konstruktor.

Namítal jsem, že ho tak utvrdíme v mylném dojmu, že předek, tedy třída Presenter, vlastně žádné své závislosti nemá, jelikož si programátor v konstruktoru předává jen ty své. Ale jde víceméně o banalitu.

Horší je, že konstruktor potomka je volán nad objektem, který ještě není plně inicializován, tj. nejsou mu předány závislosti rodiče. Zkrátka se volá ještě před voláním inject(). Pokud by uživatel v konstruktoru použil funkci, která nějakou takovou závislost vyžaduje (například getUser()), výsledkem bude zavádějící chybová hláška Fatal Error: Call to a member function xyz on a non-object nebo nějaká podobná. Což není dobře.

Kdyby se uživatel zeptal, jak má tedy situaci vyřešit a v konstruktoru plné inicializace docílit, odpovědí by bylo: nejde to, přesuň kód do metody startup(). (Pokud nejsem srozumitelný, podívejte se na příklad níže.) Tímto problémem řešení 3a netrpí.

ad 5) injektování přímo do proměnných s anotací @inject by bylo vůbec nejšikovnější. Naprosté minimum režijního kódu, konec problémů s dědičností. Má to ale svá úskalí: obsah public proměnných nelze kontrolovat, private proměnné nejsou součástí veřejného API (jak je plnit?) nicméně k tomuto tématu se ještě v nějakém dalším článku vrátím.

Nakonec jsme se s Vaškem Purchartem domluvili na šalamounském řešení 4, kdy se všechny závislosti budou předávat konstruktorem s tím, že závislosti třídy Presenter sbalíme do jediného objektu PresenterDependencies, ten už se bude předávat snadno a při změně závislostí Presenteru se nic nerozbije.

Pokud budu chtít do svého presenteru předat objekt ArticleFacade, bude to vypadat takto:

class ArticlePresenter extends Presenter
{
	function __construct(Nette\Application\UI\PresenterDepedencies $pd, ArticleFacade $af)
	{
		parent::__construct($pd);
		$this->articleFacade = $af;

		// nyní je objekt plně inicializován a lze volat třeba:
		if (!$this->getUser()->isLoggedIn()) { ... }
	}
}

Podstatné je, že všechny závislosti budou předány přímo v konstruktoru, objekt bude ihned inicializován a nebudou se zavádět žádné novoty v podobě metod inject. Jak Vašek psal, jde o ústupek pohodlnosti v zájmu přehlednosti: „Většinou v Nette vyhrávala vždy pohodlnost … mám za to, že v rámci plánovaných změn a refaktoringů by Nette mělo tuhle houpačku mírně zhoupnout směrem k přehlednosti za cenu absolutní pohodlnosti.“

Poznámka: aby bylo evidentní, že třída PresenterDependencies je jen workaround nedostatku v návrhu jazyka a že vlastně nemá sémantický význam v objektovém modelu, nevadilo by mi ji implementovat takto triviálně:

class PresenterDependencies
{
	private $services;

	function __construct(Application\Application $application, ..., Security\User $user)
	{
		$this->services = func_get_args();
	}

	function get()
	{
		return $this->services;
	}
}


abstract class Presenter
{
	function __construct(PresenterDependencies $pd)
	{
		list($this->application, ..., $this->user) = $pd->get();
	}

}

Konec dobrý, všechno dobré. Vašek připravil pull request a já si uvědomil…

Slepá ulička

…že tohle řešení je vlastně nejhorší ze všech. Proč?

Udělal jsem si výčet faktorů, které chci u jednotlivých řešení sledovat (pořadí nesouvisí s důležitostí):

  • pohodlnost
  • intuitivnost
  • blbuvzdornost
  • moment inicializace
  • rozšiřitelnost (dědičnost)

ad pohodlnost: Povinné předání objektu PresenterDependencies je zdánlivá drobnost, ale troufám si tvrdit, že tohle by byl důvod, proč mnozí DI nepoužijí a raději si ArticleFacade vytáhnou ze service locatoru. Uvedený type hint je nejdelší název třídy z celého frameworku a psát ho je za trest.

Představil jsem si sám sebe, jak tohle ukazuju na školení a připadal bych si jako kokot. Jako bych školil nějaký Zend. Musel bych se ptát sám sebe, proč to neděláme nějak lépe.

ad intuitivnost: Honza položil otázkou, co se stane, pokud člověk začne věc řešit intuitivně. Dopadne to špatně. Nenapadne ho, že existuje nějaké PresenterDepedencies a že je ho potřeba předat. Nepředá ho, Presenter nezíská závislosti a bude v poloinicializovaném stavu. V nečekané chvíli vyskočí nějaký divoký nicneříkající Fatal Error.

S tím velmi úzce souvisí otázka blbuvzdornosti. I když budu vědět, že musím volat parent::__construct() (vždycky volejte!), mohu na to snadno zapomenout a bude problém. V případě presenteru by bylo možné (a vhodné) implementovat kontrolu, zda byl volán, stejně jako je tomu u metody startup(), ale problém je, že řešení 4 jde této chybě naproti.

Programátor také může udělat chybu v type hintu (všimli jste si, že jsem ji v příkladu udělal?) nebo jej neuvede vůbec. Sice jsem se snažil poladit chybové hlášky DI kontejneru, ale ty ve své obecnosti nikdy nemohou být dokonalé a programátor bude stejně zmaten.

Pod momentem inicializace myslím okamžik, kdy je objekt plně inicializován. PresenterDependencies ho dokázal dostat do konstruktoru, jiné řešení tohle neumí. Nicméně výhodu v případě presenterů relativizuje fakt, že uživatelé jsou stejně zvyklí úvodní kód dávat do metody startup(), kde už objekt plně inicializován je.

Pátý a dosud opomíjený faktor hodnotí, jak se řešení vypořádá s existencí více úrovní dědičnosti. Což je v případě Nette obvyklé, tj. vytvořit si nějaký abstraktní BasePresenter a teprve od něj dědit. Co když bude mít BasePresenter také nějaké závislosti? Řešení 4 předpokládá, že bychom je také uváděli v konstruktoru třídy BasePresenter a znovu opakovali v jeho potomcích. Otravnost by se tím zvyšovala.

Ven ze slepé uličky

Udělal jsem si tabulku, která u každého řešení uvádí, jak vyhovuje jednotlivým faktorům. Jasným vítězem se ukázalo být 3c. To konstruktor vůbec nepoužívá a každá třída si své závislosti předá metodou (nejlépe finální) s názvem začínajícím na inject. (Viz implementace.)

PresenterFactory, která má za úkol vytvářet instance presenterů, postupuje tak, že po vytvoření objektu zavolá všechny jeho metody inject v pořadí od třídy Presenter k potomkům. Tedy programátor si v každé třídě vytvoří metodu nazvanou inject() nebo podobně, a v ní uvede jednotlivé závislosti. Nemusí předávat nic navíc, minimalizuje se možnost chyby. Z hlediska pohodlnosti, rozšiřitelnosti a blbuvzdornosti je tak učiněno za dost.

Typický příklad předávání závislostí do presenterů bude v Nette vypadat takto:

class BasePresenter extend Presenter
{
	function injectBase(User $user, ...)
	{
		$this->user = $user;
		...
	}
}

class ArticlePresenter extends BasePresenter
{
	function inject(ArticleFacade $af)
	{
		$this->articleFacade = $af;
	}
}

A co z hlediska intuitivnosti? Co když uživatel použije pro předání závislostí konstruktor, protože o žádných metodách inject() neví? Nevadí, fungovat to bude. Pochopitelně v konstruktoru nebude stále možné volat např. metodu getUser(), ale tuto daň rád zaplatím.

Celá věc by šla zjednodušit injektováním přímo do proměnných, třeba časem k tomu najedeme klíč. Do té doby se mi 3c jeví nejschůdnější.

Komentáře

  1. Milo #1

    V PHP 5.4 by šlo ReflectionClass::newInstanceWithoutConstructor. V PHP5.3 mě napadla velká čuňárna:

    class Test
    {
    	protected $abc;
    	protected $foo = '1';
    
    	public function __construct()
    	{
    		echo __METHOD__ . PHP_EOL;
    	}
    }
    
    $args = array(
    	"\x00*\x00abc" => 'Abcdef',
    	"\x00*\x00foo" => '123456',
    );
    
    $serArgs = serialize($args);
    $className = 'Test';
    $serObj = 'O:' . strlen($className) . ':"' . $className . '"' . substr($serArgs, 1);
    
    $obj = unserialize($serObj);
    $obj->__construct();

    Nebo všechny argumenty null a následně inject().

    před 12 lety
  2. Jan Tichý #2

    avatar

    Já bych jenom upřesnil, že jsem preferoval obojí možnosti 3b a 3c podle chuti a zvyklostí programátora, jenom aby mu v tom framework neházel klacky pod nohy, což je přesně tohle Tvé výsledné řešení. Moc se mi to líbí. Díky!

    před 12 lety
  3. Vena #3

    avatar

    Celkově se mi na nette nelíbí, že všechny presentéry musí dědit nějakou třídu. Příjde mi to strašně nešikovné. Pokud chci, aby můj presentér dědil od nějaké moji třídy, tak to moc jednoduše nejde, vzhledem k tomu, že PHP nepodporuje vícenásobnou dědičnost. Já bych to celé předělal takto:

    Toto by byl nějaký presentér:

    class ArticlePresenter implements Presenter {
        /** @PresenterContext */
        private $context;
    
        ...
    }

    Toto by bylo rozhraní Presenter:

    interface Presenter {}

    Do proměnné $context by se nainjektoval nějaky Presenter context se stejnýma metodama, které se teďka dědí ze třídy Presenter. A měl by rovnou nainjektované i závislosti, takže by to programátor nemusel řešit ve svém kódu.

    Pokud se ti z nějakého důvodu nelíbí injektování do private proměnné (soudě dle twitteru se ti to očividně nelíbí), tak můžeš do rozhraní Presenter přidat metodu setPresenterContext a programátor už by si tu metodu implementoval podle sebe. Což ale není tak pěkné.

    Sorry jestli jsem netrefil syntaxi PHP, dlouho jsem v něm nic nedělal.

    před 12 lety | reagoval [4] David Grudl
  4. David Grudl #4

    avatar

    #3 Veno, Presenter nemusí dědit třídu UI\Presenter, stačí, pokud implementuje rozhraní IPresenter s jedinou metodou. Příkladem je třeba MicroPresenter, který používám u malých webů.

    Injektování do private proměnné se mi nelíbí z prostého důvodu: takové proměnné nelze zvenčí vůbec nastavit (pomineme-li triky). Privátní proměnná není ani součástí veřejného API, takže se nedá hovořit o deklaraci závislosti. To, že to nějak dokáže zařídit kontejner, znamená, že vytváříme v kódu závislost na kontejneru, čímž se porušuje DI.

    před 12 lety | reagoval [6] Vena
  5. arron #5

    Jak je to s větší hloubkou dědění. Běžně se dostávám minimálně na 3. úroveň (BasePresenter → BaseAdminPresenter → ArticleAdminPresenter). Řeší se to v ArticleAdminPresenter také normálně přes metodu inject() s tím, že volám metodu předka?

    před 12 lety | reagoval [19] David Grudl
  6. Vena #6

    avatar

    #4 Davide Grudle,
    To s tim rozhraním IPresenter jsem nevěděl, dobré vědět.

    Injektování do private proměnné – Nastaví ji kontejnér. Žádnou závislost na kontejnéru v kódu nevytváříme, když oanotujeme proměnnou nějakou dostatečně abstraktní anotací (třeba @Inject), případně jenom deklarujeme typ proměnné (anotací @var) a necháme na DI kontejnéru, aby injektoval podle typu.
    A nevidím důvod, proč by závislosti měli být součásti API. DI obecně porušuje princip zapouzdření, protože musí vědět o závislostech nějaké třídy, které ji chce nainjektovat. Proč to ještě navíc tahat do API?

    před 12 lety | reagoval [9] Braňo [10] v6ak
  7. jasir #7

    avatar

    Mělo by být asi facade, nikoli fascade.

    před 12 lety | reagoval [17] David Grudl
  8. petnovcz #8

    avatar

    Co zvolit obojí řešení – injectování pomocí metod inject*() a jako workaround možnost anotace @inject u property? Víceméně po vzoru frameworku Flow3 – https://web.archive.org/…agement.html

    před 12 lety
  9. Braňo #9

    avatar

    #6 Veno, DI princíp zapúzdrenia neporušuje, ten hovorí iba o tom, že správanie objektu možno ovplyvniť iba prostredníctvom jeho API. Pravdepodobne máte na mysli princíp skrývania informácií, ktorý hovorí o tom, že objekt by mal svoje implementačné detaily skryť za svojim API. Závislosti objektu však nie sú implementačný detail. Ide o deklaráciu toho, aké služby objekt požaduje od iných objektov, aby mohol plniť svoju jedinú zodpovednosť. Preto by mali byť súčasťou API objektu.

    Injektovanie do privátnej premennej je nesprávne. Aj keď zvolíme dostatočne všeobecnú anotáciu, nedá sa bežnými jazykovými konštrukciami táto premenná naplniť. Samotný kód teda nie je závislý na kontajneri, ale zrazu máme programátora závislého na použití kontajnera. Keď chceme takýto objekt použiť bez kontajnera (napr. v unit testoch), máme problém. Povedal by som, že závislosť na kontajneri sa vytvára, len nie je taká zrejmá na prvý pohľad.

    před 12 lety | reagoval [11] Vena
  10. v6ak #10

    avatar

    #6 Veno, Nedá se svítit. Při zapojení televize potřebuju vědět, jestli ji mám zapojit do nějaké zásuvky (případné do jaké), nebo jestli ji mám dopřát sluneční svit či něco jiného. (Sluneční svit by teda mohl být porušením single responsibility principle, ale dejme tomu, že jej televize umí nějak přímo využít.) Na druhou stranu, jakmile je správně „vytvořena“ (jsou ji zajištěny podmínky), nezáleží nám na tom.

    Z logiky věci jde o API třídy, ne o API objektu. Konstruktor ale nepatří do API objektu, protože jej nemůžeme volat na nějakém objektu, který dostaneme. (To, že nám PHP toto umožní, je věc druhá. Logicky to nedává smysl.) Proto nemusí být konstruktor kompatibilní s konstruktorem předka – pomomek může chtít nějaké informace navíc. Nemusí ani ctít viditelnost konstruktoru předka, takže může být i privátní. Dokonce jej neuvádíme do interface (opět: PHP to dovolí, ale…). Když totiž s nějakým objektem pracuju, je mi jedno, jak byl vytvořen (nezajímá mě konstruktor).

    Ono to je i tak pragmatické: Jak to udělat jinak? Mám se pokaždé zeptat nějaké superchytré továrny, aby mi podle svého centrálního plánu rozumně rozhodla, zda je lepší ArrayList, nebo LinkedList? Jak to pozná? (Továrny nezavrhuju zcela, v některých specifických případech to může fungovat.)

    TL;DR: V konstruktoru to nevadí.

    před 12 lety | reagoval [12] Vena
  11. Vena #11

    avatar

    #9 Braňo,
    Podle mě závislosti jsou implementační detail. Viz třeba:

    class ArticleService {
        public function __construct(PDO $datasource) {
            ...
        }
    }

    A teď cokdyž se jednoho dne rozhodnu, že nechci používat PDO? Jak to udělám, aniž bych musel měnit API?
    Tady je o tomto docela zajímavá diskuze.

    Injektování do private proměnné – naplnit se dá pomocí reflexe. Samozřejmě je lepší použít nějaky framework, já jen že na něm kód nezávisí.
    Co se testování týče – umět nějak jednoduše přistupovat k private proměnným by podle měl umět každý testovací framework.

    před 12 lety | reagoval [13] Braňo [16] v6ak
  12. Vena #12

    avatar

    #10 v6aku,
    Víceméně souhlasím. Já jen že nechápu tu averzi k injektáži do private proměnných. A už vůbec nechápu argumentaci, že je to špatně, protože to pak není součástí API.

    před 12 lety | reagoval [16] v6ak
  13. Braňo #13

    avatar

    #11 Veno, V tomto prípade to tak môže vyzerať, keďže sa deklaruje závislosť na konkrétnej implementácii, nie na rozhraní. Predstavte si uvedený príklad s rozhraním DataSource namiesto triedy PDO. Potom by deklarácia podľa mňa mala byť súčasťou API – objekt deklaruje, že pre splnenie svojej zodpovednosti požaduje služby od iného objektu, ktorý mu dodá dáta. Ja to za implementačný detail nepovažujem a teda by som ho ani nechcel zakryť.

    Reflexia sa určite použiť dá, písal som o bežných jazykových konštrukciách. Aby sa objekty dali rozumne používať, bez toho frameworku (kontajnera) sa potom nezaobídem. Takže kód na ňom prakticky závislý je.

    před 12 lety | reagoval [14] Vena [15] Vena
  14. Vena #14

    avatar

    #13 Braňo,
    Ad první odstavec: Pokud existuje nějaké společné rozhraní, tak pak ano. Ale né vždy to tak je, obzvlášť v PHP.

    Ad druhý odstavec: Nesouhlasím.

    před 12 lety
  15. Vena #15

    avatar

    #13 Braňo,
    Btw nevěděl jsem, že PDO je implementační třída, jak říkám, v PHP nejsem zrovna zběhlý.
    Měl jsem na mysli případ, kdy třeba chceš vyměnit PDO za Dibi nebo něco takového. Snad toto není úplně mimo příklad, myslím si, že je jasné, co tím chci ríct.

    před 12 lety
  16. v6ak #16

    avatar

    #12 Veno, Předně se musíme domluvit, o jakém API je řeč:

    1. API třídy – to, co můžu volat i bez konkrétní instance, tedy zejména statické metody a konstruktor (nepřímo přes operátor new)
    2. API objektu – to, co mužů volat, až mám instanci. Tedy zejména nestatické metody.

    Krása zapouzdření je v tom, že odněkud dostanu nějaký objekt a mohu s ním pracovat, aniž bych se zajímal o jeho detaily. Přiznání zavislostí v konstruktoru tomu nezabrání.

    Na druhou stranu, závislosti na databázovém spojení (v #11 Vena) se nezbavíš. Někde se vždy projeví. Může si to třída obstarat odněkud magicky (pořád ale při přechodu třeba z PDO na DibiConnection tu ta závislost bude, jen bude hůře vidět) a může tu závislost přiznat. Já tvrdím, že je lepší závislost přiznat. Dokonce v případě statický typovaného jazyka se nekompatibilní změna projeví v případě konstruktoru nemožností to zkompilovat. Změna použitého DB spojení je v každém případě nekompatibilní změnou (z hlediska API té třídy, ne z hlediska API objektu), nemuzeme-li to vyřešit wrapperem.

    Jak jinak by se mělo řešit, že nějaký objekt bude chtít mít k dispozici DB spojení? Není možné říct „chci DB spojení, přiděl mi nějaké“ – různá DB spojení nejsou snadno zaměnitelná a toto může fungovat jen v jednodušších případech.

    před 12 lety | reagoval [18] Vena
  17. David Grudl #17

    avatar

    #7 jasire, díky, opravil jsem.

    ad private property injection: Z mého pohledu jde o protimluv (co je potom private?), porušení základního principu OOP (jak se to vypořádá s definicí zapouzdření?) a smyslu Dependency Injection, jakožto techniky, kdy se každá třída veřejně hlásí ke svým závislostem (private znamená neveřejný) za účelem odstranění skrytých závislostí (tady vznikla závislost na DI kontejneru).

    Každopádně diskuse by měla patřit spíš sem.

    před 12 lety | reagoval [20] Vena
  18. Vena #18

    avatar

    #16 v6aku,
    Už to začíná být spíš věc názoru :)

    Každopádně – z mojeho osobního pohledu mi injektáž do private attributu příjde lepší – vím, že ta závislost tam někde bude tak jako tak, ale raděj, když bude jenom v nějakém konfigu, kde nemusí zajímat klientský kód, než aby byla vidět v API (ano, sice jenom v API třídy, ale pořád je to API, musí se to zdokumentovat a bude to vidět). Nevidím důvod, abych závislost přiznával, je to implementační vec a nevím, k čemu klientskému kódu bude.

    Ale je mi jasné, že DI prostě zapouzdření porušovat musí, stejně jako AOP, dědičnost apod. viz odkazovaná diskuze na stackoverflow. Já jen že nevidím důvod, abych se k tomu veřejně přes API přiznával :)

    před 12 lety
  19. David Grudl #19

    avatar

    #5 arrone, pro každou třídu si vytvoříš jinak nazvanou metodu inject.

    před 12 lety
  20. Vena #20

    avatar

    #17 Davide Grudle,
    DI obecně porušuje princip zapouzdření, viz diskuze.
    DI jako technika, kdy se třída veřejně hlásí ke svým závislostem – tuto definici DI slyším poprvé.
    Závislost na kontejnéru – máš na mysli tu anotaci? Ta není třeba, můžeš využít nějakou konfiguraci, která se do kódu nepromítne (XML, neon…).

    před 12 lety | reagoval [21] David Grudl
  21. David Grudl #21

    avatar

    #20 Veno, pod DI si představujeme každý něco diametrálně jiného. Zatímco já si allows a choice of component to be made at run-time rather than compile time vykládám tak, že v API získáme možnost komponentu změnit, ty si to vykládáš tak, že se pomocí reflexe nabouráme do objektu a tam mu něco podstrčíme. Toliko k private property injection, myslím, že téma je vyčerpáno.

    před 12 lety
  22. Aleš Roubíček #22

    avatar

    DI má tu krásnou vlastnost, že závislosti nejen zjevně deklaruje, ale i ukazuje na chyby v návrhu. Pokud mám moc parametrů v konstruktoru, neznamená to, že je mám zabalit do parameter objektu, nebo se je tam snažit dostat nějak jinak, ale že samotná třída, která má tolik závislostí, má příliš mnoho zodpovědností a je náchylná k tomu být nestabilní a zranitelnou.

    před 12 lety | reagoval [23] David Grudl
  23. David Grudl #23

    avatar

    #22 Aleši Roubíčku, máš pochopitelně pravdu, zároveň jsi se ale zcela minul tématem článku. Ten totiž vůbec neřeší, že má Presenter závislostí moc a je bezezbytku platný i v případě, že Presenter má závislost třeba jen jednu.

    před 12 lety
  24. jtousek #24

    avatar

    Mohu se zeptat z jakého důvodu bylo injektování do proměnných (způsobem uvedeným v další článku) pro tuto chvíli zavrženo a jaké má nevýhody?

    před 12 lety | reagoval [25] David Grudl
  25. David Grudl #25

    avatar

    #24 jtousku, DI kontejner v Nette dovoluje injektovat přímo do proměnných, ale nedělá to automaticky podle anotace @inject. Zavrženo to nebylo, je to otevřené téma.

    před 12 lety | reagoval [26] jtousek
  26. jtousek #26

    avatar

    #25 Davide Grudle, V tom případě proč toto řešení nebylo použito rovnou? Z uvedených se mi zamlouvá nejvíce, ale zřejmě má určité nevýhody, které nejsou vidět na první pohled.

    před 12 lety
  27. roudrunner #27

    avatar

    >…, protože o žádných metodách inject() neví…

    Stalo se. Nemůžu průběžně sledovat vývoj Nette i když bych rád. Stáhnu si novou verzi, protože potřebuju po delší době něco napsat a v příkladu s CD sbírkou vidím:

    public function inject(AlbumRepository $albums)
    {
    	$this->albums = $albums;
    }

    Hledám, co to je a bohužel nikde v celé širé dokumentaci nenacházím důležitou větu z tohoto článku: „… po vytvoření objektu zavolá všechny jeho metody inject v pořadí od třídy Presenter k potomkům…“

    Chápu, že psát dokumentaci je nuda, ale prosím dejte do ní někam aspoň ten kousek odstavce z části „Ven ze slepé uličky“, aby bylo jasné, že metody začínající „inject“ jsou zavolány automaticky.

    před 11 lety

Tento článek byl uzavřen. Už není možné k němu přidávat komentáře.


phpFashion © 2004, 2024 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.