Poznámky k dependency injection v Nette
- Honza Marek
- Člen | 1664
Místo abych na poslední sobotě flejmvéroval, k čemuž bylo vyzýváno, tak jsem si moudra ušetřil na teď a aktualizoval svoje poznámky o DI v Nette
- Patrik Votoček
- Člen | 2221
jelikož pod stránkou nejde komentovat tak musím sem…
- pamatovat na situaci, že továrničkou na servisy může být i metoda jiné servisy
zajímavá myšlenka hodně zajímavá
IContainer
nějak mě schází metoda addAlias (možná i removeAlias)
Autowiring
souhlas zkoušel jsem nějakou implementaci v praxy a zatím to není dostatečně uzrálé
Configurator
neměla by tohle řešit nějaká obecná/univerzální factory třída pro služby? (ideálně navěšováním callbacků)
nezaložíme hnutí pro odstranění Nette\Environment
? :-)
- Honza Marek
- Člen | 1664
Patrik Votoček napsal(a):
nějak mě schází metoda addAlias (možná i removeAlias)
Není nezbytně potřeba, dá se použít addService s definicí služby nastavenou tak, že se jedná o alias jiné služby.
Configurator
Zkusil jsem to v tom dokumentu dovysvětlit.
nezaložíme hnutí pro odstranění
Nette\Environment
? :-)
Pro návrh. Letáková kampaň? Stránka na FB?
- juzna.cz
- Člen | 248
Procetl jsem si velkou cast DI container ze Symfony (https://github.com/…e/master/lib) a musim uznat, ze to je opravdu prvotridni prace! (graf trid muzete videt na http://juzna.cz/…-symfony.svg, pokud by to nekoho zajimalo)
Rozdeleni na Container, Builder a Loadery se mi opravdu libi:
- Container je opravdu ciste jen kontejner
- Builder ho rozsiruje a umoznuje navic i lazy-vytvareni
- Loadery jsou zvlast pro kazdy format konfiguracniho souboru; dostanou Builder a ten nakonfiguruji
Navic je zajimavy napad ze slidu Fabiena (slide 56), aby bylo mozne do tovarnicky poslat znovu samotny container (pokud bude chtit tovarnicka sama nejak vytvaret dalsi sluzby); coz ale asi trosku protireci samotnemu DI, i kdyz to podle me neni take az tak jasne.
- juzna.cz
- Člen | 248
ad Scope v Honzovych poznamkach: pokud jsem to pochopil spravne, tak v symfony to je to same, jako je ted v nette signleton (tzn. ne tak jako to ty pises), cili ze si ServiceBuilder pamatuje co vytvoril a nedela to znovu. V symfony to je take pojmenovane jako „shared“ (Fabien ve slidech upozornuje, ze v tomto pripade nejde o singleton, coz ale nevim proc)
- juzna.cz
- Člen | 248
V podstate tomu DI containeru ze Symfony chybi jen toto:
- „načítat proměnné prostředí a konfiguraci“
- „zrušení parametru run a možná jeho nahrazení univerzálnější tagováním služeb“
- „autowiring“
jinak je to IMHO presne to, co se ted snazime vymyslet :) (nebo teda presne to, co jsem se snazil vymyslet ja)
- Patrik Votoček
- Člen | 2221
Příště by jsi mohl studovat modernější verzi. :-) https://github.com/…ncyInjection jinak DI je v podstatě odpověď na to proč zrušit Environment (ptal jsi se jinde – doufám že jsi to byl ty :-D ).
- Honza Marek
- Člen | 1664
juzna.cz napsal(a):
V podstate tomu DI containeru ze Symfony chybi jen toto:
- „načítat proměnné prostředí a konfiguraci“
Do Symfony DI containeru se dají nastavit tzv. parametry, které pak jdou použít pro definici služeb.
- „zrušení parametru run a možná jeho nahrazení univerzálnější tagováním služeb“
To má.
- „autowiring“
Tohle opravdu bohužel nemá.
- Yrwein
- Člen | 45
Chápu-li dobře to, co míníte autowiringem – imho by autowiring šel nahradit (z části) tzv. interface injection – kde pro určité interfacy nastavíte metody, které se mají volat a s čím (prostě jako u klasické služby) a pak jen v konfiguraci klasicky zadáte služby bez nějaké další omáčky – builder už sám pozná, jestli třídy interface implementují a zachová se dle toho. (Ad odkaz – je to psáno v době, kdy tuto funkcionalitu Symfony2 DI neobsahoval, tak se nenechte zmást. .])
(Ale samozřejmě chápu, že pravý autowiring (ala ObjectManager z Flow3), kde si dle nějaké konvence container vše pozná sám, to úplně nenahradí; už jenom kvůli constructor injection.)
Editoval Yrwein (3. 3. 2011 9:58)
- hason
- Člen | 23
Autowiring v Symfony2:
S autowiringem jsou spojena značná úskalí (např. požadované rozhraní implementuje více služeb, magie ;)), proto není v DIC z Symfony2 výslovně obsažen. Ale to jen na první pohled.
DIC z Symfony2 obsahuje podporu pro kompilátory, kterou napsal Johannes M. Schmitt (stejně jako podporu pro scope). Pomocí kompilátorů lze libovolně upravit proces vytváření service containeru. Lze tak implementovat i autowiring. Při vytváření SC jsou známy veškeré třídy, takže lze doupravit celou definici služby pomocí reflekcí. Podobný princip používáme při vytváření repository objektů pro Doctrine 2 (pomocí tagů si označíme služby, jejichž definici následně upravíme pomocí kompilátoru dle dané „šablony“, abychom se neupsali :)). Pomocí tohoto principu kompilátorů také zajišťujeme autowiring služeb do controllerů.
DIC z Symfony2 obsahuje také možnost dependency injection pomocí rozhraní, kterou napsal Bulat Shakirzyanov. Hodně jsme ji využívali, ale stejnou funkčnost lze zařídit pomocí kompilátoru, které jsou univerzálnější.
Osobně si myslím, že psát znovu DIC pro PHP je mrháním sil (rozumnější mi přijde přispívat do hotových řešení, Symfony2, FLOW3, …). Symfony2 je nyní rozděleno na nezávislé komponenty, které lze využít (podobně jako PEAR balíčky). Git repozitář pro DIC ze Symfony2 je na adrese https://github.com/…ncyInjection .
Editoval hason (3. 3. 2011 11:13)
- Filip Procházka
- Moderator | 4668
Proč by neseděl do frameworku? Věřím tomu, že Symfonní DI půjde do
Nette napasovat. Hlavně je potřeba zbavit se Environment
a
zrefaktorovat, popř. odstranit Configurator
- Honza Marek
- Člen | 1664
hason napsal(a):
Lze tak implementovat i autowiring. Při vytváření SC jsou známy veškeré třídy, takže lze doupravit celou definici služby pomocí reflekcí.
Myslíš, že bys mohl předvést nějakou ukázku? Přece jen než tohle vyzkoumám sám, zabere mi to asi mnohem víc času.
- hason
- Člen | 23
Úplně primitivní kompilátor (kontroly na zacyklení referencí snad dělají již standardní kompilátory) by mohl vypadat následovně (netestováno, psáno z hlavy).
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class AutowiringPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$classes = $this->getClasses($container);
$parameterBag = $container->getParameterBag();
foreach ($container->getDefinitions() as $definition) {
if (!$definition->isPublic()) {
continue;
}
$class = $parameterBag->resolveValue($definition->getClass());
$reflection = new ReflectionClass($class);
$constructor = $reflection->getConstructor();
if ($constructor->isPublic() && count($definition->getArguments()) === 0) {
$arguments = array();
foreach ($constructor->getParameters() as $parameter) {
$arguments[] = $this->getParameterValue($parameter, $classes);
}
$definition->setArguments($arguments);
}
}
}
protected function getClasses(ContainerBuilder $container)
{
$classes = array();
$parameterBag = $container->getParameterBag();
foreach ($container->getDefinitions() as $id => $definition) {
if (!$definition->isPublic()) {
continue;
}
$class = $parameterBag->resolveValue($definition->getClass());
$reflection = new ReflectionClass($class);
if ($reflection) {
$classes[$reflection->getName()][] = $id;
foreach ($reflection->getInterfaceNames() as $interface) {
$classes[$interface][] = $id;
}
$parent = $reflection;
while (($parent = $parent->getParentClass())) {
$classes[$parent->getName()][] = $id;
}
}
}
return $classes;
}
/**
* @magic :)
*/
protected function getParameterValue(ReflectionParameter $parameter, array $classes)
{
if (($reflection = $parameter->getClass())) {
$class = $reflection->getName();
if (isset($classes[$class]) && count($classes[$class]) === 1) {
$value = new Reference($classes[$class][0]);
} elseif ($parameter->isDefaultValueAvailable()) {
$value = $parameter->getDefaultValue();
} else {
throw new Exception();
}
} elseif ($parameter->isDefaultValueAvailable()) {
$value = $parameter->getDefaultValue();
} else {
throw new Exception();
}
return $value;
}
}
Tento kompilátor se pak zaregistruje do ContainerBuilderu:
$builder->addCompilerPass(new AutowiringPass());
Editoval hason (7. 3. 2011 9:40)
- Honza Marek
- Člen | 1664
Díky, hasone. Až na pár drobností to šlape hned. Jaká je licence toho kódu? :)