php的依賴注入容器

這裏接着上一篇 php依賴注入,直接貼出完整代碼以下:php

<?php

class C
{
    public function doSomething()
    {
        echo __METHOD__, '我是C類|';
    }
}

class B
{
    private $c;

    public function __construct(C $c)
    {
        $this->c = $c;
    }

    public function doSomething()
    {
        $this->c->doSomething();
        echo __METHOD__, '我是B類|';
    }
}
class A
{
    private $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }

    public function doSomething()
    {
        $this->b->doSomething();
        echo __METHOD__, '我是A類|';;
    }
}

//這段代碼使用了魔術方法,在給不可訪問屬性賦值時,__set() 會被調用。讀取不可訪問屬性的值時,__get() 會被調用。
class Container { private $s = array(); function __set($k, $c) { $this->s[$k] = $c; } function __get($k) { return $this->s[$k]($this); } }
$class = new Container(); $class->c = function () { return new C(); }; $class->b = function ($class) { return new B($class->c); }; $class->a = function ($class) { return new A($class->b); }; // 從容器中取得A $model = $class->a; $model->doSomething(); // C::doSomething我是C類|B::doSomething我是B類|A::doSomething我是A類|

再來一段簡單的代碼演示一下,容器代碼來自simple di container,完整以下:html

<?php

class C
{
    public function doSomething()
    {
        echo __METHOD__, '我是C類|';
    }
}

class B
{
    private $c;

    public function __construct(C $c)
    {
        $this->c = $c;
    }

    public function doSomething()
    {
        $this->c->doSomething();
        echo __METHOD__, '我是B類|';
    }
}
class A
{
    private $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }

    public function doSomething()
    {
        $this->b->doSomething();
        echo __METHOD__, '我是A類|';;
    }
}
class IoC
{
    protected static $registry = [];

    public static function bind($name, Callable $resolver)
    {
        static::$registry[$name] = $resolver;
    }

    public static function make($name)
    {
        if (isset(static::$registry[$name])) {
            $resolver = static::$registry[$name];
            return $resolver();
        }
        throw new Exception('Alias does not exist in the IoC registry.');
    }
}

IoC::bind('c', function () {
    return new C();
});
IoC::bind('b', function () {
    return new B(IoC::make('c'));
});
IoC::bind('a', function () {
    return new A(IoC::make('b'));
});


// 從容器中取得A
$foo = IoC::make('a');
$foo->doSomething(); // C::doSomething我是C類|B::doSomething我是B類|A::doSomething我是A類|

這段代碼使用了後期靜態綁定git

依賴注入容器的高級功能

真實的dependency injection container會提供更多的特性,如github

  • 自動綁定(Autowiring)或 自動解析(Automatic Resolution)數組

  • 註釋解析器(Annotations)php7

  • 延遲注入(Lazy injection)閉包

下面的代碼在Twittee的基礎上,實現了Autowiring。ide

<?php
class C
{
    public function doSomething()
    {
        echo __METHOD__, '我是周伯通C|';
    }
}

class B
{
    private $c;

    public function __construct(C $c)
    {
        $this->c = $c;
    }

    public function doSomething()
    {
        $this->c->doSomething();
        echo __METHOD__, '我是周伯通B|';
    }
}

class A
{
    private $b;

    public function __construct(B $b)
    {
        $this->b = $b;
    }

    public function doSomething()
    {
        $this->b->doSomething();
        echo __METHOD__, '我是周伯通A|';;
    }
}

class Container
{
    private $s = array();

    public function __set($k, $c)
    {
        $this->s[$k] = $c;
    }

    public function __get($k)
    {
        // return $this->s[$k]($this);
        return $this->build($this->s[$k]);
    }

    /**
     * 自動綁定(Autowiring)自動解析(Automatic Resolution)
     *
     * @param string $className
     * @return object
     * @throws Exception
     */
    public function build($className)
    {
        // 若是是匿名函數(Anonymous functions),也叫閉包函數(closures)
        if ($className instanceof Closure) {
            // 執行閉包函數,並將結果
            return $className($this);
        }

        /** @var ReflectionClass $reflector */
        $reflector = new ReflectionClass($className);

        // 檢查類是否可實例化, 排除抽象類abstract和對象接口interface
        if (!$reflector->isInstantiable()) {
            throw new Exception("Can't instantiate this.");
        }

        /** @var ReflectionMethod $constructor 獲取類的構造函數 */
        $constructor = $reflector->getConstructor();

        // 若無構造函數,直接實例化並返回
        if (is_null($constructor)) {
            return new $className;
        }

        // 取構造函數參數,經過 ReflectionParameter 數組返回參數列表
        $parameters = $constructor->getParameters();

        // 遞歸解析構造函數的參數
        $dependencies = $this->getDependencies($parameters);

        // 建立一個類的新實例,給出的參數將傳遞到類的構造函數。
        return $reflector->newInstanceArgs($dependencies);
    }

    /**
     * @param array $parameters
     * @return array
     * @throws Exception
     */
    public function getDependencies($parameters)
    {
        $dependencies = [];

        /** @var ReflectionParameter $parameter */
        foreach ($parameters as $parameter) {
            /** @var ReflectionClass $dependency */
            $dependency = $parameter->getClass();

            if (is_null($dependency)) {
                // 是變量,有默認值則設置默認值
                $dependencies[] = $this->resolveNonClass($parameter);
            } else {
                // 是一個類,遞歸解析
                $dependencies[] = $this->build($dependency->name);
            }
        }

        return $dependencies;
    }

    /**
     * @param ReflectionParameter $parameter
     * @return mixed
     * @throws Exception
     */
    public function resolveNonClass($parameter)
    {
        // 有默認值則返回默認值
        if ($parameter->isDefaultValueAvailable()) {
            return $parameter->getDefaultValue();
        }

        throw new Exception('I have no idea what to do here.');
    }
}

// ----
$class = new Container();
$class->b = 'B';
$class->a = function ($class) {
    return new A($class->b);
};
// 從容器中取得A
$model = $class->a;
$model->doSomething(); 


$di = new Container();
$di->php7 = 'A';
/** @var A $php7 */
$foo = $di->php7;
var_dump($foo);

$foo->doSomething(); //C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A|object(A)#10 (1) { ["b":"A":private]=> object(B)#14 (1) { ["c":"B":private]=> object(C)#16 (0) { } } } C::doSomething我是周伯通C|B::doSomething我是周伯通B|A::doSomething我是周伯通A|

?>

以上代碼的原理參考PHP官方文檔:反射,PHP 5 具備完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔註釋。函數

若想進一步提供一個數組訪問接口,如$di->php7能夠寫成$di'php7'],則需用到[ArrayAccess(數組式訪問)接口 oop

一些複雜的容器會有許多特性,歡迎博友們補充。

相關文章
相關標籤/搜索