Poznámky k dependency injection v Nette

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

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

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

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

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

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

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

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

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

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

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)

juzna.cz
Člen | 248
+
0
-

Takze Symfonni DIC tedy umi vsechno, co chceme. Proc ho tedy opravdu nevyuzit?

Jedinnou vyhradu vydim v tom, ze nesedi do Nette frameworku. To by se bud

  • dalo ozelet
  • prrejmenovat tridy na Nette\DI\neco
  • udelat nejaky mapping na nette tidy pomoci extends?
Filip Procházka
Moderator | 4668
+
0
-

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

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

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

Patrik Votoček
Člen | 2221
+
0
-

trochu sem poladil DI v Nelle

Honza Marek
Člen | 1664
+
0
-

Díky, hasone. Až na pár drobností to šlape hned. Jaká je licence toho kódu? :)

jtousek
Člen | 951
+
0
-

V repozitáři Nelly je LICENSE.txt obsahující LGPL.

grey
Člen | 94
+
0
-

jtousek wrote:

V repozitáři Nelly je LICENSE.txt obsahující LGPL.

Myslím, že to byla reakce na uživatele hason, ne na Patrika ;)

jtousek
Člen | 951
+
0
-

grey napsal(a):

jtousek wrote:

V repozitáři Nelly je LICENSE.txt obsahující LGPL.

Myslím, že to byla reakce na uživatele hason, ne na Patrika ;)

Aha, no jo mě to nedošlo. :) Taky mi bylo divný, že by si nevšiml toho texťáku. Srry. ;)

hason
Člen | 23
+
0
-

Díky, hasone. Až na pár drobností to šlape hned. Jaká je licence toho kódu? :)

BSD