[2011–05–05] Finalizace Dependency Injection

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

HosipLan: teorie je fajn, teď by to chtělo nějakou praktickou ukázku z praxe, tradiční přístup vs. DI :-)

David Grudl
Nette Core | 8218
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

Nechceš si radši dohledat na googlu něco jako „why are static classes evil?“? :)

arron
Člen | 464
+
0
-

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
+
0
-

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.

Cifro
Člen | 245
+
0
-

http://pimple-project.org/ – A simple Dependency Injection
Container for PHP 5.3

Patrik Votoček
Člen | 2221
+
0
-

Doporučuju vám mrknout na tyto slidy http://www.slideshare.net/…nzendcon2010

David Grudl
Nette Core | 8218
+
0
-

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.

nanuqcz
Člen | 822
+
0
-

Díky za odpovědi. Cítím, že pomalu mi to docvakává, ale než to docvakne úplně, budu tomu muset ještě hodně pomoct.

P.S. A mohl by tu přednášku o DI případně někdo natočit, pokud bude? Nebo mě fakt donutíte přijet z Ostravy :-)

bojovyletoun
Člen | 667
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

I přes to že jsem tady psal Proč vlastně params není Nette\ArrayHash ? tak…

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
+
0
-

Ří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
+
0
-

Já bych možná setter vyhodil úplně. Přece jenom:

$container->foo = $objA;
$container->foo = $objB; // throw exception
Filip Procházka
Moderator | 4668
+
0
-

Ty settery jsou magické, getter stačí :)

frosty22
Člen | 373
+
0
-

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
+
0
-

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
+
0
-

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
+
0
-

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)

Filip Procházka
Moderator | 4668
+
0
-

Petr Motejlek: Tak proto mi to nikdy nefungovalo! Díky :)

frosty22
Člen | 373
+
0
-

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)

nanuqcz
Člen | 822
+
0
-

Ona se ta služba při každém volání vytvoří nová? =-O Můžu se zeptat, jaký to má praktický účel?

Jan Tvrdík
Nette guru | 2595
+
0
-

Proč by to mělo vytvořit dvě instance Connection?

frosty22
Člen | 373
+
0
-

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
+
0
-

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)

nanuqcz
Člen | 822
+
0
-

Tak teď jsem zmatený…

$a = $context->myService1;
$b = $context->myService1;

Kolik instancí myService1 je teď ve skutečnosti vytvořeno?

Jan Tvrdík
Nette guru | 2595
+
0
-

Jedna, proboha! Co bys asi dělal se dvěma.

Acci
Člen | 83
+
0
-

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
+
0
-

@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
+
0
-

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
+
0
-

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
+
0
-

@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
+
0
-

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
+
0
-

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); }
?>
nanuqcz
Člen | 822
+
0
-

manik napsal(a):

Tohle vůbec není DI, přístup, který zde používáš (načítání věcí z context) se nazývá ServiceLocator.

A k čemu je tedy proměnná $this->context v presenteru vůbec přístupná? Jak jinak bys v presenteru použil nějakou službu?

22
Člen | 1478
+
0
-

může to prosím někdo z adminů přesunout do https://forum.nette.org/…cy-injection..
Ď

Filip Procházka
Moderator | 4668
+
0
-

$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.

nanuqcz
Člen | 822
+
0
-

Jan Tvrdík napsal(a): Jedna, proboha! Co bys asi dělal se dvěma.

HosipLan napsal(a): $this->context je v Presenteru v pořádku.

Díky, uklidnili jste mě :-)

Ani
Člen | 226
+
0
-

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
+
0
-

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)?

mkoubik
Člen | 728
+
0
-

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.

Elijen
Člen | 171
+
0
-

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
+
0
-

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)