Jak předávat závislosti do presenterů, komponent a jiných služeb?
- David Grudl
- Nette Core | 8228
Současná dokumentace nepopisuje téma Dependency Injection zrovna
šťastně, věnuje totiž hodně prostoru DI kontejneru, ale
nejdůležitější téma, tedy jak předávat závislosti, není popsané
dostatečně. Už
je, díky Vojto!
Doporučil bych vám proto pustit si přednášku Filipa Procházky či přečíst DI a předávání závislostí a rozhodně článek Vojty Dobeše Tvorba komponent s využitím autowiringu.
Jaké je best practice pro předávání závislostí presenterům, komponentám a jiným službám v Nette?
Nezávisle na frameworku a typu tříd vždy platí totéž:
- povinné závislosti konstruktorem
- volitelné buď samostatnou metodou, nebo též konstruktorem
Tečka.
Nicméně existuje situace, kdy je předávání závislostí konstruktorem
problematické, tzv. constructor
hell, tedy situace, kdy předáváme závislosti konstruktorem určité
třídě a zároveň jejímu potomkovi. Tohle nastává často u presenterů,
kde je právě zvykem mít nějaký BasePresenter. Jako workaround dává DI
kontejner v Nette možnost použít v BasePresenteru místo konstruktoru
metodu nazvanou injectBase()
(případně jakkoliv jinak, jen musí
začínat na inject
).
Jde o workaround pro base presentery, takže v jiných situacích metody inject nepoužívejte.
Dále předávání závislostí přes konstruktory nebo jiné metody je
otravné v tom, že musíte napsat nějaký rutinní kód. Nutnost psát
rutinní kód vždy ukazuje na slabé místo samotného jazyka, jedna ze šancí na zjednodušení byla zamítnuta, nicméně
některé IDE ho umí vygenerovat a taktéž DI kontejner ve frameworku nabízí
zkratku v podobě @inject
anotací. Public (!) proměnnou
doplníte anotací např. /** @var Model\UserFacade @inject */
a
framework sám do ní vloží službu a vyhnete se psaní konstruktoru nebo
inject metody.
Protože to není čisté řešení, funguje opět jen pro presentery. A existuje ještě špinavější hack, property lazy-autowire na steroidech. Jejich používání rozhodně není best practice, ale je to sakra pohodlné.
- David Grudl
- Nette Core | 8228
Video už by mělo fungovat. Filipovo řešení funguje i s
protected
proměnnýma a používá lazy loading, tedy služba se
načítá teprve v okamžiku, kdy se k proměnné přistoupí.
- David Matějka
- Moderator | 6445
@Ripper: @inject
zavislosti vklada hned pri vytvareni
objektu, @autowire
je lazy, z kontejneru se zavislosti vytahnou az
kdyz je potrebujes. kdyby/autowired
ma navic jeste predavani
zavislosti do createComponent*
metod
EDIT: jeste si dovolim self promo, cheatsheet o di v nette
Editoval matej21 (15. 5. 2014 14:28)
- Šaman
- Člen | 2665
Ripper napsal(a):
Jaký je rozdíl mezi anotací @inject a @autowire z Filipova řešení? Jinak video je soukromé a nejde spustit.
Filipovo řešení nevyžaduje, aby byla property public. Řeší to pomocí černé magie. Anotace @inject je obyčená property injection, tedy zápis do public proměnné a jediné, co se nám na ni nelíbí je porušení zapouzdření (na druhou stranu presentery jsou velmi specifické a důsledně je zapouzdřovat nemá až tak smysl).
- Šaman
- Člen | 2665
@David Grudl: Jediné, co se mi ve stávajícím Nette nedaří uspokojivě vyřešit je jak dostat do BaseControl službu, aniž by se jí museli potomci zabývat?
Editoval Šaman (15. 5. 2014 15:30)
- David Matějka
- Moderator | 6445
@Šaman: muzes si napsat CompilerExtension, ktery v beforeCompile
pozapina u (vsech) sluzeb inject
- enumag
- Člen | 2118
Když už byl zmíněn článek https://doc.nette.org/…tion/factory, postrádám v něm klíčovou informaci jak té RegistrationControl předat jiný než výchozí IMailer či jinou službu (a nepodařilo se mi toho žádným způsobem docílit).
Editoval enumag (16. 5. 2014 9:49)
- Vojtěch Dobeš
- Gold Partner | 1316
services:
-
implement: IRegistrationControlFactory
arguments: [..., ..., @myCustomMailer]
myCustomMailer:
class: # ...
(neřekl bych, že je to klíčová informace, aplikace obvykle využívají defaultní varianty služeb, ale jako tip by se to tam přidat dalo, souhlasím)
- enumag
- Člen | 2118
@vojtech.dobes: Díky, tohle funguje. Já to zkoušel takhle:
services:
- IRegistrationControlFactory( @myCustomMailer ) # neměl jsem jiné argumenty
Což skončilo naprosto prapodivnou chybou protože ta IRegistrationControlFactory nakonec vůbec nevracela to co měla ale kdovíproč vracela ten @myCustomMailer.