[2011–05–05] Finalizace Dependency Injection
- David Grudl
- Nette Core | 8218
Naopak, DI je poměrně dost složitá věc. Jako jednoduchá se zdá teprve ve chvíli, kdy ji člověk pochopí (jako ostatně vše). Proto v Nette existoval a bude existovat jak DI, tak Service Locator.
- Filip Procházka
- Moderator | 4668
Na tom něco bude Davide :D
Ukázka, jak se propracovat z relativně „Bad-oldschool“ způsobu na novější a lepšejší … https://gist.github.com/976155 (není to perfektní ani best-practice!! jenom malá ukázka, krok kupředu!)
Pokud by někdo chtěl navést jak přepracovat konrétní případy (prosím osekané na nezbytné minimum) může založit téma v příslušné sekci fóra a ti, co se jim to zdá jednoduché, by mohli poradit, jak to přepsat :)
Editoval HosipLan (17. 5. 2011 11:00)
- nanuqcz
- Člen | 822
HospiLan: Díky za praktickou ukázku :-)
Takže Nette\DI\Container
slouží k přístupu ke třídám,
které chci mít k dispozici kdekoli v aplikaci? Jak se to potom liší od
používání statických tříd?
Viz. HospiLanův příklad:
// Místo
$this->foo = $this->context->models->foo->find($id);
// můžu použít
$this->foo = Foo::find($id);
Výsledek je stejný, o načtení třídy Foo se postará RobotLoader,
odpadá zbytečné vytváření MyModelsLocator
a
MyServiceFactories
(které určitě sežerou trošku té paměti,
stejně tak fakt, že díky registraci služeb naroste v paměti o něco málo
i proměnná $this->context
).
Neberte mě špatně, snažím se DI přijít na kloub stejně, jako jsem kdysi přišel k Nette a tím i k MVC (taky jsem v MVC neviděl výhody, a teď bez toho nemůžu žít :-) )
- Filip Procházka
- Moderator | 4668
Během vývoje myšlení (programátora) se postupně dostaneš do fáze, kdy zjistíš, že když máš funkce ve třídě, není to nezbytně ještě objektové programování. Objevíš věci jako zapouzdřenost, polymorfizmus, … A pak ti dojde že statické třídy jsou zlo :) Potom třeba objevíš testování a už je nebudeš chtít ani cítit.
Občas se hodí na nějaké pomocníčky a zkratky, ale to je tak všechno. Obecně jsou tfuj :)
PS: nic se nemá brát jako dogma
- nanuqcz
- Člen | 822
Během vývoje myšlení (programátora) se postupně dostaneš do fáze, kdy zjistíš, že když máš funkce ve třídě, není to nezbytně ještě objektové programování.
S tím souhlasím a věřím ti ;-)
Objevíš věci jako zapouzdřenost, polymorfizmus, …
Dobře, pořád ale nevidím konkrétní přínos použití
Nette\DI\Container
ve výše odkazovaném příkladě.
P.S. Nemám si na to založit vlastní vlákno třeba v Obecné diskuzi?
- Filip Procházka
- Moderator | 4668
Nechceš si radši dohledat na googlu něco jako „why are static classes evil?“? :)
- arron
- Člen | 464
xxxObiWan napsal(a):
Během vývoje myšlení (programátora) se postupně dostaneš do fáze, kdy zjistíš, že když máš funkce ve třídě, není to nezbytně ještě objektové programování.
S tím souhlasím a věřím ti ;-)
Objevíš věci jako zapouzdřenost, polymorfizmus, …
Dobře, pořád ale nevidím konkrétní přínos použití
Nette\DI\Container
ve výše odkazovaném příkladě.P.S. Nemám si na to založit vlastní vlákno třeba v Obecné diskuzi?
Mam jeden uplne super zasadni pripad a tim jsou unit testy :-)
Unit test nesmi (nebo by nemel, uz z jeho povahy) pristupovat do
databaze…jak to udelat? V Tvem druhem pripade neresitelna situace. Pokud
pouzivas DI, tak jenom nekde v configu zmenis vytvareni sluzby
‚databaseConnection‘ ze tridy Database
na
FakeDatabase
a hotovo:-)
- Šaman
- Člen | 2659
HosipLan: Nechtěl bys o tom něco povědět na poslední sobotě, pls? Nebo někdo jiný, komu DI připadá jednoduché? Taková úvodní přednáška do DI, motivace, výhody – něco jako kdysi byla přednáška o pětivrstném modelu. Tehdá mi ukázala cestu a dneska je to standart, i když konkrétní implementace se tam moc nerozebírala.
- Patrik Votoček
- Člen | 2221
Doporučuju vám mrknout na tyto slidy http://www.slideshare.net/…nzendcon2010
- David Grudl
- Nette Core | 8218
xxxObiWan napsal(a):
Takže
Nette\DI\Container
slouží k přístupu ke třídám, které chci mít k dispozici kdekoli v aplikaci? Jak se to potom liší od používání statických tříd?
Ne, k tomu slouží tzv. service locator, v řeči Nette třída Environment. DI\Container je naopak lokální záležitost, která tvoří objekty a předává jim přes konstruktor nebo settery závislosti.
- bojovyletoun
- Člen | 667
Ahoj, po delší době si prohlížím zdrojáky a dívám se na DI, už to
konečně pro mě není tajemná zkratka. Ale teprv tomu začínám rozumět.
Vlastně ze zajímavosti mě zajímá tato věc:
‚Proč některé služby se vypadají takto:
function createServiceSession($c){return new Session($c->httpRequest, $c->httpResponse);}
‘
a některé takto
createServiceApplication($c){$context= new DI\Container;$context->addService($c->...;
return new Application($context);}
Jde mi o to, proč někdy, když předávám službě jinou službu, tak jednou se předají rovnou vybrané služby a podruhé se předá kontejner s vybranými službami. Je v tom nějaký rozdíl? Případně to bude předmětem refactoringu? Hraje v tom roli to, že „objekty o kontejneru nemusí vědět“ (citace z FabPot slidu o DI).
Taky jsem se díval do createServiceUser – tam je lazy vytváření
služeb přes lambda funkce. Soudím, že služby se tedy jinak lazy
nevytváří (sice služba se spustí při getService, ale teď mám na mysli,
když chci předat službu jako v createApplication, taky ty služby se už
spustí, protože
$context->addService('...',$container->s...)
volá getService
a spustí ji tedy). Myslím jen předání služby aniž by se spustila (jako
v user přes closure), ale zařídit to rovnou v containeru a vyvyrovat se
používání lambda funkcí).
- David Grudl
- Nette Core | 8218
Pokud je to možné, tak je vždy lepší předávat jednotlivé služby, jako v případě Session. V případě User je užitečné předávat služby líně a proto se místo služeb předávají továrničky (ano, je to starý dobrý kontejner, jen v pozici chytré továrny. Implementovat a instancovat extra továrnu např. na Authenticator by stálo stejný výkon, jako rovnou vytvořit Autenticator, takže by to nemělo efekt).
Lazy předání služeb lambda funkce řeší poměrně elegantně, vyšlo mi
to lépe než nějaké copyService
. Navíc se to dělá spíš
zřídka.
- Ani
- Člen | 226
Mě se tohle pojetí DI líbí, nikomu to nic nenutí. Jestli budu injectovat přes constructor, setter, nebo constuctor s kontejnerem je každýho věc a špatně není ani jedno. Třeba já injectovaní jednotlicých služeb přes konstruktor nemám rád, protože když ma člověk složitou hiearchii tříd a vzpomene si že chce nějakou další službu do toho uplně prvního předka, tak to přepisovat je děs. A to že na nic nezapomenu v tom kontejneru si můžu v konstruktoru ošetřit. Právě proto vítám nějakou volnost.
Ale měl bych dotaz proč se teď v třídě Configurator ta property container plní tak jak se plní? Nebylo by lepší pro ni mít specialní container, který by měl ty továrny na Applicaton, Router atd. v sobě a předával by se on? Asi mi tam něco uniká.
- bojovyletoun
- Člen | 667
Na to už se někdo ptal. Jde o oddělení samotného Configuratoru a
DefaultServices. Pak by možná bylo jednodušší si přidat své služby
(nemusel by se dědit Configurator), ale stačilo by něco v bootstrapu jako
$configurar= new Environment::getConfigurator();$cofigur->load(new DefaultServices);$confifur->load('myservices1.neon);$configor->load(new MyServices2);
Je to jen nápad, nezkoušel jsem to. load by automaticky rozlišil, zda je
o string nebo objekt
- Ondřej Mirtes
- Člen | 1536
Ani napsal(a):
Třeba já injectovaní jednotlicých služeb přes konstruktor nemám rád, protože když ma člověk složitou hiearchii tříd a vzpomene si že chce nějakou další službu do toho uplně prvního předka, tak to přepisovat je děs.
Při používání DI kontejneru bys tohle přepsal právě jen na jednom místě v configu.
- Patrik Votoček
- Člen | 2221
I přes to že jsem tady
psal Proč vlastně params není
tak…Nette\ArrayHash
?
Narážím na problém:
$container->params['foo'] = array("Foo", "Bar");
$container->params['foo'][] = "Baz";
Editoval Patrik Votoček (21. 5. 2011 8:44)
- David Grudl
- Nette Core | 8218
Říkám si, jestli je skutečně účelné, aby __set byl zkratkou pro addService(). Nevypadá tohle příliš divně?
$container->template = 'Nette\Templating\FileTemplate';
dump($container->template); // vrátí string nebo objekt FileTemplate?
$container->database = function() { return ... };
dump($container->database); // vratí objekt Closure?
Setter by měl být přímočařejší, tj. umět vkládat jen objekty
samotné, nikoliv továrničky. Pak by
$container->template = 'FileTemplate'
vyhodil výjimku a
$container->database = function() {}
vložil jako službu objekt
Closure.
Co myslíte?
- Patrik Votoček
- Člen | 2221
Já bych možná setter vyhodil úplně. Přece jenom:
$container->foo = $objA;
$container->foo = $objB; // throw exception
- frosty22
- Člen | 373
Zeptám se trošku mimo, avšak pokud někdo používá NetBeans, jak řešíte našeptávač metod/property daných instancí objektů, jelikož pokud si vytáhnu z kontajneru instanci objektu, tak bohužel NetBeans neví o jakou třídu jde, vzhledem k dynamickému vrácení třídy např. getService(„database“)
Editoval frosty22 (6. 6. 2011 17:11)
- Ondřej Brejla
- Člen | 746
Napiš si nad to phpDoc:
/** @var MyDatabaseClass $database */
$database = $this->context->database... // or whatever
Mělo by to fungovat. Snad.
- Filip Procházka
- Moderator | 4668
Já to dělám trošku oklikou.. V base presenteru napíšu annotaci, která „přehodí“ našeptávání na moji vlastní třídu Container, kde mám vyjmenovány základní služby. Ostatní bych asi řešil jako Ondra :)
- Petr Motejlek
- Člen | 293
Kdysi jsem si zvykl psát tu anotaci až pod definici hodnoty proměnné, protože NetBeans si občas usmyslel, že si dokáže napovídání odvodit podle toho, co do té proměnné dávám ;).
<?php
$main = $this->getContext()->mainDibiConnection;
/** @var \DibiConnection $main */
?>
Editoval Petr Motejlek (7. 6. 2011 10:41)
- frosty22
- Člen | 373
Zdravím,
snažím se pochopit celkovou problematiku DI, avšak trošku jsem na
vážkách, co se týče praktické části. V praxi tedy:
v boostrapu si vytvořím kontajner se všemi modely například metodou callbacků:
<?php
$container->addService("Database", function($c) { return new Connection; }
$container->addService("Users", function($c) { return new Users; }
$container->addService("Articles", function($c) { return new Articles; }
?>
následně v presenteru, když budu chtít přistupovat k těmto modelům, a ty modely jsou navázány na databázi:
<?php
public function actionDetail() {
$this->template->articles = $this->context->Users->getLastRegisterUsers();
$this->template->users = $this->context->Articles->getNewArticles();
}
?>
Pro příklad v duchu DI, je toto správně? V případě, že ano, jakým způsobem docílím, aby se předalo spojení do databáze daným modelům Users a Articles?
Logicky podle úvodního příspěvku by to tedy šlo takto:
<?php
$container->addService("Database", function($c) { return new Connection; }
$container->addService("Users", function($c) { return new Users($c->Database); }
$container->addService("Articles", function($c) { return new Articles($c->Database); }
?>
Avšak toto způsobí, že se vytvoří dvě instance Connection, což je očividně nežádoucí, a řešení je dle tedy slajdů v prezentaci (link viz výše), využitím statické proměnné, čili:
<?php
$container->addService("Database", function($c) {
static $conn;
if (is_null($conn)) $conn = new Connection;
return $conn;
}
?>
Oproti tomu je zde namítáno, že statické třídy jsou zlo (vím toto třída není), avšak tedy jak to správně má být?
Děkuji velice za nakopnutí a utříbení myšlenek, doufám, že neplácám úplně z cesty ;(
Editoval frosty22 (14. 6. 2011 0:51)
- frosty22
- Člen | 373
Aha, source kontajneru jsem přímo nestudoval, čili při každém volání getService(…) se vždy vrací stejná instance třídy? čili v podstatě všechny služby jsou singletony?
V tomto případě, jsem opravdu pochopil DI jinak nežli je myšleno. V tomto případě, pokud chci vytvářet nové instance modelů, tak k tomu tento kontajner určen není? Jsem předpokládal, že DI slouží k celkovému odstínění všech volání konkrétních tříd na jednu pozici, a tudíž by v presenterech bylo vždy např:
$form = $this->context->appform;
$session = $this->context->session;
prostě žádné „new“ :)
Ale v tomto případě, je to alternativní logika funkčnosti doposavadního Environment.
- Jan Tvrdík
- Nette guru | 2595
frosty22 wrote:
V tomto případě, pokud chci vytvářet nové instance modelů, tak k tomu tento kontajner určen není?
Proč by k tomu neměl být?
Editoval Jan Tvrdík (14. 6. 2011 1:35)
- Acci
- Člen | 83
Já to dělám trošku oklikou.. V base presenteru napíšu annotaci, která „přehodí“ našeptávání na moji vlastní třídu Container, kde mám vyjmenovány základní služby. Ostatní bych asi řešil jako Ondra :)
Psát anotace ručně je pruda. Já to mám naprogramováno tak, že kontroluji změnu souboru se definicí služeb (nejjednodušeji čekám ve smyčce s kontrolou filemtime) a v případě změny vygeneruji automaticky anotace @property-read pro kontejner. IDE navíc našeptává název služby a zná samozřejmě i její třídu.
- Filip Procházka
- Moderator | 4668
@frosty22: můj ty smutku… Proč ti vadí, že budeš mít vždy právě jednu instanci svého modelu? To je naprosto v pořádku.
- frosty22
- Člen | 373
HosipLan napsal(a):
@frosty22: můj ty smutku… Proč ti vadí, že budeš mít vždy právě jednu instanci svého modelu? To je naprosto v pořádku.
Ano jistě, tak proti tomu nic nemám, avšak jistě se může stát, že je potřeba vytvořit další instanci nějakého objektu, proto jsem se dotazal, zda je to s tímto možné, či zda to k tomu není určené.
Příklad active record:
<?php
$user = $c->getService("Users");
$user->name = "Karel";
$user->save();
$user = $c->getService("Users");
$user->name = "Honza";
$user->save();
?>
V případě nových instancí vytvoří dva inserty, takhle vytvoří insert a update..
Editoval frosty22 (14. 6. 2011 8:05)
- Filip Procházka
- Moderator | 4668
ActiveRecord je anti-pattern k Dependency Injection. Nejde ho s ním používat. Čili pokud máš modely jako ActiveRecord, ztrácí smysl snažit se je nacpat do Containeru.
- Nox
- Člen | 378
@frosty22 ale ;-)
1 instance vracená z containeru != singleton
načtení služby z containeru != new
Problém s new je že nemůžeš kontrolovat co se předá, pokud máš vytváření zapouzdřeno v kontejneru, tak to přece kontroluješ. Ať to vrací stejnou instanci nebo ne, z tohoto hlediska je to jedno a třída sama to nepozná jestli je daný objekt použitý jinde
Sice mě to ještě taky trochu mate, ale…asi je to tak správně…
Editoval Nox (14. 6. 2011 8:31)
- VasekPurchart
- Člen | 20
frosty22 napsal(a):
<?php public function actionDetail() { $this->template->articles = $this->context->Users->getLastRegisterUsers(); $this->template->users = $this->context->Articles->getNewArticles(); } ?>
Pro příklad v duchu DI, je toto správně? V případě, že ano, jakým způsobem docílím, aby se předalo spojení do databáze daným modelům Users a Articles?
Tohle vůbec není DI, přístup, který zde používáš (načítání věcí z context) se nazývá ServiceLocator.
- frosty22
- Člen | 373
Začínám tomu snad již taky přicházet na kloub, pouze ted mě mate, proč načítání z contextu není DI přístup, přeciž je zde určitě něco jako:
<?php
class Presenter {
protected $context;
public function __construct($c)
{
$this->context = $c;
}
}
$container->addService("Presenter", function($c) { return new Presenter($c); }
?>
Což tedy předá onen kontajner presenteru, což je snad contructor injection .. akorát asi tedy je problém v tom, že nejsou přesně určené objekty k nimž objekt Presenter přistupuje nýbrž se mu předá celý kontajner?
Správně by tedy mělo být? :
<?php
class Presenter {
public function __construct(Model $model, Dalsi $dalsi)
{
...
}
}
$container->addService("Presenter", function($c) { return new Presenter($c->model, $c->dalsi); }
?>
- Filip Procházka
- Moderator | 4668
$this->context
je v Presenteru v pořádku.
A nemusíš ho dávat do Containeru. Na to je Nette\Application\PresenterFactory. Tu si můžeš implementovat vlastní a tam si právě můžeš „ještě čistěji“ vytvořit Presenter, který Container nebude potřebovat, ale dostane jen svoje služby.
- Ani
- Člen | 226
frosty22 napsal(a):
$form = $this->context->appform;
$session = $this->context->session;prostě žádné „new“ :)
Ale v tomto případě, je to alternativní logika funkčnosti doposavadního Environment.
Injectujes nejakou službu. O vytváření nových instancí se postará ta služba.
$form = $this->context->formManager->getNewForm();
Ten příspěvek od manik v 8:54 bych smazal, každýho kdo neví to jenom poplete.
Editoval Ani (14. 6. 2011 13:31)
- Elijen
- Člen | 171
ActiveRecord je anti-pattern k Dependency Injection. Nejde ho s ním používat. Čili pokud máš modely jako ActiveRecord, ztrácí smysl snažit se je nacpat do Containeru.
Anti-pattern? Vždyť obojí řeší naprosto něco jiného. Proč by active record nemohl používat dependency injection (např. pro připojení k DB)?
- Elijen
- Člen | 171
mkoubik napsal(a):
Protože ActiveRecord je založený na získávání entit pomocí statických metod. Ty jsou tight-coupled úplně se vším už z definice.
Možná tě špatně chápu, ale tohle je dle mého názoru otázka konkrétní implementace AR. V Doctrine 1.3 např:
$users = new \Users();
$users->name = 'Karel';
$users->save();
Editoval Elijen (14. 6. 2011 15:17)
- Filip Procházka
- Moderator | 4668
Neříkám, že ActiveRecord je anti-pattern sám o sobě, ale navzájem se vylučuje s Dependency Injection.
Editoval HosipLan (14. 6. 2011 15:36)