PHP程序員如何理解依賴注入容器(dependency injection container)
背景知識
傳統的思路是應用程序用到一個Foo類,就會創建Foo類并調用Foo類的方法,假如這個方法內需要一個Bar類,就會創建Bar類并調用Bar類的方法,而這個方法內需要一個Bim類,就會創建Bim類,接著做些其它工作。
- <?php
- // 代碼【1】
- class Bim
- {
- public function doSomething()
- {
- echo __METHOD__, '|';
- }
- }
- class Bar
- {
- public function doSomething()
- {
- $bim = new Bim();
- $bim->doSomething();
- echo __METHOD__, '|';
- }
- }
- class Foo
- {
- public function doSomething()
- {
- $bar = new Bar();
- $bar->doSomething();
- echo __METHOD__;
- }
- }
- $foo = new Foo();
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
使用依賴注入的思路是應用程序用到Foo類,Foo類需要Bar類,Bar類需要Bim類,那么先創建Bim類,再創建Bar類并把Bim注入,再創建Foo類,并把Bar類注入,再調用Foo方法,Foo調用Bar方法,接著做些其它工作。
- <?php
- // 代碼【2】
- class Bim
- {
- public function doSomething()
- {
- echo __METHOD__, '|';
- }
- }
- class Bar
- {
- private $bim;
- public function __construct(Bim $bim)
- {
- $this->bim = $bim;
- }
- public function doSomething()
- {
- $this->bim->doSomething();
- echo __METHOD__, '|';
- }
- }
- class Foo
- {
- private $bar;
- public function __construct(Bar $bar)
- {
- $this->bar = $bar;
- }
- public function doSomething()
- {
- $this->bar->doSomething();
- echo __METHOD__;
- }
- }
- $foo = new Foo(new Bar(new Bim()));
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
這就是控制反轉模式。依賴關系的控制反轉到調用鏈的起點。這樣你可以完全控制依賴關系,通過調整不同的注入對象,來控制程序的行為。例如Foo類用到了memcache,可以在不修改Foo類代碼的情況下,改用redis。
使用依賴注入容器后的思路是應用程序需要到Foo類,就從容器內取得Foo類,容器創建Bim類,再創建Bar類并把Bim注入,再創建Foo類,并把Bar注入,應用程序調用Foo方法,Foo調用Bar方法,接著做些其它工作.
總之容器負責實例化,注入依賴,處理依賴關系等工作。
代碼演示 依賴注入容器 (dependency injection container)
通過一個最簡單的容器類來解釋一下,這段代碼來自 Twittee
- <?php
- class Container
- {
- private $s = array();
- function __set($k, $c)
- {
- $this->s[$k] = $c;
- }
- function __get($k)
- {
- return $this->s[$k]($this);
- }
- }
這段代碼使用了魔術方法,在給不可訪問屬性賦值時,__set() 會被調用。讀取不可訪問屬性的值時,__get() 會被調用。
- <?php
- $c = new Container();
- $c->bim = function () {
- return new Bim();
- };
- $c->bar = function ($c) {
- return new Bar($c->bim);
- };
- $c->foo = function ($c) {
- return new Foo($c->bar);
- }; //Vevb.com
- // 從容器中取得Foo
- $foo = $c->foo;
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
這段代碼使用了匿名函數
再來一段簡單的代碼演示一下,容器代碼來自simple di container
- <?php
- 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('bim', function () {
- return new Bim();
- });
- IoC::bind('bar', function () {
- return new Bar(IoC::make('bim'));
- });
- IoC::bind('foo', function () {
- return new Foo(IoC::make('bar'));
- });
- // 從容器中取得Foo
- $foo = IoC::make('foo');
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
這段代碼使用了后期靜態綁定
依賴注入容器 (dependency injection container) 高級功能
真實的dependency injection container會提供更多的特性,如:
自動綁定(Autowiring)或 自動解析(Automatic Resolution)
注釋解析器(Annotations)
延遲注入(Lazy injection)
下面的代碼在Twittee的基礎上,實現了Autowiring。
- <?php
- class Bim
- {
- public function doSomething()
- {
- echo __METHOD__, '|';
- }
- }
- class Bar
- {
- private $bim;
- public function __construct(Bim $bim)
- {
- $this->bim = $bim;
- }
- public function doSomething()
- {
- $this->bim->doSomething();
- echo __METHOD__, '|';
- }
- }
- class Foo
- {
- private $bar;
- public function __construct(Bar $bar)
- {
- $this->bar = $bar;
- }
- public function doSomething()
- {
- $this->bar->doSomething();
- echo __METHOD__;
- }
- }
- 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.');
- }
- }
- // ----
- $c = new Container();
- $c->bar = 'Bar';
- $c->foo = function ($c) {
- return new Foo($c->bar);
- };
- // 從容器中取得Foo
- $foo = $c->foo;
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
- // ----
- $di = new Container();
- $di->foo = 'Foo';
- /** @var Foo $foo */
- $foo = $di->foo;
- var_dump($foo);
- /*
- Foo#10 (1) {
- private $bar =>
- class Bar#14 (1) {
- private $bim =>
- class Bim#16 (0) {
- }
- }
- }
- */
- $foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething
以上代碼的原理參考PHP官方文檔:反射,PHP 5 具有完整的反射 API,添加了對類、接口、函數、方法和擴展進行反向工程的能力。 此外,反射 API 提供了方法來取出函數、類和方法中的文檔注釋。
新聞熱點
疑難解答