這裏接着上一篇 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
一些複雜的容器會有許多特性,歡迎博友們補充。