Rozšíření DI o Property Injection

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

DI v Nette je super, ale když jsem ho uviděl poprvé, tak jsem si přesto říkal něco mi tam chybí. Tou dobou jsem pracoval hodně se svým frameworkem (no spíš jsem ho podědil a udržoval v chátrání :)) a tam jsem si zvykl, že properties mám vyplněné od startu (natvrdo, žádné DI). Teď celý projekt předělávám do Nette(v plánu jsou takové změny, že je jednodušší to celé přepsat) a po přečtení článku Dependency Injection a property injection jsem hned věděl co mi chybí „Property Injection“. Proto jsem vytvořil úplně jednoduché (asi za 30min) Property Injection kontejner do Nette a PIObject který tuto injekci umožní.

Kontejner

<?php
class PIContainer extends \Nette\DI\Container{

    /**
     * Names of injected services.
     * @var array
     */
    private $PIregistry = array();

    /**
    * Gets the service object by name.
    * @param  string
    * @return object
    */
    public function getService($name){
        $service = parent::getService($name);
        if($service instanceof PIObject && !in_array($name,$this->PIregistry)){
            $this->PIregistry[] = $name;
            $reflection = $service->getReflection();
            $properties = $reflection->getProperties();
            foreach($properties as $p){
                if($reflection->getProperty($p->name)->hasAnnotation("inject")){
                    $service->injectProperty($p->name, $this->getByType($reflection->getProperty($p->name)->getAnnotation("var")));
                }
            }
            $service->LockPI();
        }
        return $service;
    }
}
?>

PIObject

<?php
class PIObject extends \Nette\Object {
    /**
     * Set to TRUE when PI is done.
     * @var Boolean
     */
    private $PILocked = false;

    /**
     * Inject $val to object property $name.
     * @param String $name
     * @param Mixed $val
     */
    public function injectProperty($name,$val){
        if($this->PILocked){
            return;
        }
        $this->{$name} = $val;
    }

    /**
     * Lock PI, no more PIs can be done
     */
    public function LockPI(){
        $this->PILocked = true;
    }
}
?>

Objekt, kterému bude do $property injektována služba typu \Some\Service

<?php
class MyObject extends PIObject{
    /**
     * @var \Some\Service
     * @inject
     */
    protected $property;
}
?>

Property Injection je velmi zajímavá a často (například v Jave) využívaná vlastnost. Jsem zvědav jestli se oběví v budoucích verzích Nette (nebo už tam je a já ji nenašel?).

mkoubik
Člen | 728
+
0
-

Tenhle přístup jde přímo proti Dependency Injection. Měl bys psát kód tak, aby fungoval nezávisle na DI kontejneru. Bez použití kontejneru, nebo nějaké reflection magie bys ten tvůj objekt použil jak? Asi nijak, protože bys službu do protected atributu prostě nedostal. Radši si udělej private property a k ní setter.
Navíc těžko můžeš očekávat, že každá služba bude dědit od nějaké konkrétní třídy, aby to vůbec fungovalo.

Editoval mkoubik (9. 7. 2012 0:20)

chatoooo
Člen | 6
+
0
-

mkoubik napsal(a):

Tenhle přístup jde přímo proti Dependency Injection. Měl bys psát kód tak, aby fungoval nezávisle na DI kontejneru. Bez použití kontejneru, nebo nějaké reflection magie bys ten tvůj objekt použil jak? Asi nijak, protože bys službu do protected atributu prostě nedostal. Radši si udělej private property a k ní setter.
Navíc těžko můžeš očekávat, že každá služba bude dědit od nějaké konkrétní třídy, aby to vůbec fungovalo.

PI není nic proti DI. PI je jen jiný přístup(taky se nestarám kde to seženu). Nette se stane tomuto objektu hrobkou, proto není třeba uvažovat, že bude někdy použit bez reflection magie. Objekty které mají být použity bez Nette budou klasicky DI s konstruktorem který to nastaví. S tím děděním vznikne problém jedině pokud služba bude potřebovat dědit někoho jiného, pak by i ten dotyčný musel dědit PIObject. Toto vyřeší až PHP 5.4 kde by se to mohlo dat do Traits a tím by se také měl vyřešit problém s viditelností property, protože Traits by měly mít přístup i k private. Nebo tedy udělat setter ale to je už podobné jako to property udělat public.

Filip Procházka
Moderator | 4668
+
0
-

Property injection odporuje zapozdření objektu a jde přímo proti dependency injection. Už jsme o tom diskutovali několik měsíců zpátky. Juzna to udělal jako rozšíření, ale v Nette to nikdy nebude, protože je to nečistá praktika.

Neco ke čtení

// edit: Abych to doplnil, property injection samo o sobě je v pohodě. Dokud neinjektujete do private nebo protected properties.

Editoval HosipLan (9. 7. 2012 11:11)

jtousek
Člen | 951
+
0
-

@HosipLan: Ani ten Davidem navržený způsob s public properties a unset?

chatoooo
Člen | 6
+
0
-

Jediný způsob, který není proti DI, je automatické generování konstruktoru, nejlépe přímo v IDE. To by navíc mohlo umět i lazy-loading, pro který by se automaticky generoval nějaký accessor. Ale to už asi překombinované :).

jtousek
Člen | 951
+
0
-

Nestačil by ten konstruktor jednou v Nette\Object anebo v nějakém traitu (PHP 5.4)?

chatoooo
Člen | 6
+
0
-

PI je o lenosti psát konstruktor. Davidovým řešením s unsetem konstruktoru si moc tedy nepomůžu. Samozřejmě, že konstruktor Nette\Object by to možná vyřešil, ale uděláš si pro svůj objekt vlastní a stejně ho budeš muset zavolat a efekt je pryč. Navíc tu byla námitka, že nutnost dědění nemůže být očekávána. Pro mě je nejpěknější řešení přes traits, které navíc mají přístup i k private. A zapouzdření to neporušuje, je tam setter.

Filip Procházka
Moderator | 4668
+
0
-

Samozřejmě ti nikdo nebrání si to implementovat podle sebe pomocí CompilerExtension :)