理解 PHP 依赖注入

Laravel框架的依赖注入确实很强大,而且经过容器实现依赖注入能够有选择性的加载须要的服务,减小初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与你们分享,文章从开始按照传统的类设计数据库链接一直到经过容器加载服务这个高度解耦的设计展现了依赖注入的强大之处,值得咱们借鉴和学习。php

-----------------------------------------------------------分割线下面是大牛的原文----------------------------------------------------------sql

  首先,咱们假设,咱们要开发一个组件命名为SomeComponent。这个组件中如今将要注入一个数据库链接。在这个例子中,数据库链接在component中被建立,这种方法是不切实际的,这样作的话,咱们将不能改变数据库链接参数及数据库类型等一些参数。数据库

复制代码
 1 <?php
 2 
 3 class SomeComponent
 4 {
 5 
 6     /**
 7      * The instantiation of the connection is hardcoded inside
 8      * the component so is difficult to replace it externally
 9      * or change its behavior
10      */
11     public function someDbTask()
12     {
13         $connection = new Connection(array(
14             "host" => "localhost",
15             "username" => "root",
16             "password" => "secret",
17             "dbname" => "invo"
18         ));
19 
20         // ...
21     }
22 
23 }
24 
25 $some = new SomeComponent();
26 $some->someDbTask();
复制代码

为了解决上面所说的问题,咱们须要在使用前建立一个外部链接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:数组

复制代码
 1 <?php
 2 
 3 class SomeComponent
 4 {
 5 
 6     protected $_connection;
 7 
 8     /**
 9      * Sets the connection externally
10      */
11     public function setConnection($connection)
12     {
13         $this->_connection = $connection;
14     }
15 
16     public function someDbTask()
17     {
18         $connection = $this->_connection;
19 
20         // ...
21     }
22 
23 }
24 
25 $some = new SomeComponent();
26 
27 //Create the connection
28 $connection = new Connection(array(
29     "host" => "localhost",
30     "username" => "root",
31     "password" => "secret",
32     "dbname" => "invo"
33 ));
34 
35 //Inject the connection in the component
36 $some->setConnection($connection);
37 
38 $some->someDbTask();
复制代码

  如今咱们来考虑一个问题,咱们在应用程序中的不一样地方使用此组件,将屡次建立数据库链接。使用一种相似全局注册表的方式,从这得到一个数据库链接实例,而不是使用一次就建立一次。session

复制代码
 1 <?php
 2 
 3 class Registry
 4 {
 5 
 6     /**
 7      * Returns the connection
 8      */
 9     public static function getConnection()
10     {
11        return new Connection(array(
12             "host" => "localhost",
13             "username" => "root",
14             "password" => "secret",
15             "dbname" => "invo"
16         ));
17     }
18 
19 }
20 
21 class SomeComponent
22 {
23 
24     protected $_connection;
25 
26     /**
27      * Sets the connection externally
28      */
29     public function setConnection($connection){
30         $this->_connection = $connection;
31     }
32 
33     public function someDbTask()
34     {
35         $connection = $this->_connection;
36 
37         // ...
38     }
39 
40 }
41 
42 $some = new SomeComponent();
43 
44 //Pass the connection defined in the registry
45 $some->setConnection(Registry::getConnection());
46 
47 $some->someDbTask();
复制代码

  如今,让咱们来想像一下,咱们必须在组件中实现两个方法,首先须要建立一个新的数据库链接,第二个老是得到一个共享链接:框架

复制代码
 1 <?php
 2 
 3 class Registry
 4 {
 5 
 6     protected static $_connection;
 7 
 8     /**
 9      * Creates a connection
10      */
11     protected static function _createConnection()
12     {
13         return new Connection(array(
14             "host" => "localhost",
15             "username" => "root",
16             "password" => "secret",
17             "dbname" => "invo"
18         ));
19     }
20 
21     /**
22      * Creates a connection only once and returns it
23      */
24     public static function getSharedConnection()
25     {
26         if (self::$_connection===null){
27             $connection = self::_createConnection();
28             self::$_connection = $connection;
29         }
30         return self::$_connection;
31     }
32 
33     /**
34      * Always returns a new connection
35      */
36     public static function getNewConnection()
37     {
38         return self::_createConnection();
39     }
40 
41 }
42 
43 class SomeComponent
44 {
45 
46     protected $_connection;
47 
48     /**
49      * Sets the connection externally
50      */
51     public function setConnection($connection){
52         $this->_connection = $connection;
53     }
54 
55     /**
56      * This method always needs the shared connection
57      */
58     public function someDbTask()
59     {
60         $connection = $this->_connection;
61 
62         // ...
63     }
64 
65     /**
66      * This method always needs a new connection
67      */
68     public function someOtherDbTask($connection)
69     {
70 
71     }
72 
73 }
74 
75 $some = new SomeComponent();
76 
77 //This injects the shared connection
78 $some->setConnection(Registry::getSharedConnection());
79 
80 $some->someDbTask();
81 
82 //Here, we always pass a new connection as parameter
83 $some->someOtherDbTask(Registry::getConnection());
复制代码

  到此为止,咱们已经看到了如何使用依赖注入解决咱们的问题。不是在代码内部建立依赖关系,而是让其做为一个参数传递,这使得咱们的程序更容易维护,下降程序代码的耦合度,实现一种松耦合。可是从长远来看,这种形式的依赖注入也有一些缺点。ide

  例如,若是组件中有较多的依赖关系,咱们须要建立多个setter方法传递,或建立构造函数进行传递。另外,每次使用组件时,都须要建立依赖组件,使代码维护不太易,咱们编写的代码可能像这样:函数

复制代码
 1 <?php
 2 
 3 //Create the dependencies or retrieve them from the registry
 4 $connection = new Connection();
 5 $session = new Session();
 6 $fileSystem = new FileSystem();
 7 $filter = new Filter();
 8 $selector = new Selector();
 9 
10 //Pass them as constructor parameters
11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);
12 
13 // ... or using setters
14 
15 $some->setConnection($connection);
16 $some->setSession($session);
17 $some->setFileSystem($fileSystem);
18 $some->setFilter($filter);
19 $some->setSelector($selector);
复制代码

  我想,咱们不得不在应用程序的许多地方建立这个对象。若是你不须要依赖的组件后,咱们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,咱们再次返回去使用一个全局注册表来建立组件。可是,在建立对象以前,它增长了一个新的抽象层:学习

复制代码
 1 <?php
 2 
 3 class SomeComponent
 4 {
 5 
 6     // ...
 7 
 8     /**
 9      * Define a factory method to create SomeComponent instances injecting its dependencies
10      */
11     public static function factory()
12     {
13 
14         $connection = new Connection();
15         $session = new Session();
16         $fileSystem = new FileSystem();
17         $filter = new Filter();
18         $selector = new Selector();
19 
20         return new self($connection, $session, $fileSystem, $filter, $selector);
21     }
22 
23 }
复制代码

  这一刻,咱们好像回到了问题的开始,咱们正在建立组件内部的依赖,咱们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的作法。测试

  一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像咱们在前面看到的,容器做为全局注册表,使用容器的依赖注入作为一种桥梁来解决依赖可使咱们的代码耦合度更低,很好的下降了组件的复杂性:

复制代码
 1 <?php
 2 
 3 class SomeComponent
 4 {
 5 
 6     protected $_di;
 7 
 8     public function __construct($di)
 9     {
10         $this->_di = $di;
11     }
12 
13     public function someDbTask()
14     {
15 
16         // Get the connection service
17         // Always returns a new connection
18         $connection = $this->_di->get('db');
19 
20     }
21 
22     public function someOtherDbTask()
23     {
24 
25         // Get a shared connection service,
26         // this will return the same connection everytime
27         $connection = $this->_di->getShared('db');
28 
29         //This method also requires a input filtering service
30         $filter = $this->_db->get('filter');
31 
32     }
33 
34 }
35 
36 $di = new Phalcon\DI();
37 
38 //Register a "db" service in the container
39 $di->set('db', function(){
40     return new Connection(array(
41         "host" => "localhost",
42         "username" => "root",
43         "password" => "secret",
44         "dbname" => "invo"
45     ));
46 });
47 
48 //Register a "filter" service in the container
49 $di->set('filter', function(){
50     return new Filter();
51 });
52 
53 //Register a "session" service in the container
54 $di->set('session', function(){
55     return new Session();
56 });
57 
58 //Pass the service container as unique parameter
59 $some = new SomeComponent($di);
60 
61 $some->someTask();
复制代码

如今,该组件只有访问某种service的时候才须要它,若是它不须要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其余方面都不会影响到组件自己。
咱们的实现办法¶

Phalcon\DI 是一个实现了服务的依赖注入功能的组件,它自己也是一个容器。

因为Phalcon高度解耦,Phalcon\DI 是框架用来集成其余组件的必不可少的部分,开发人员也可使用这个组件依赖注入和管理应用程序中不一样类文件的实例。

基本上,这个组件实现了 Inversion of Control 模式。基于此,对象再也不以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大下降了总体程序的复杂性,由于只有一个方法用以得到所须要的一个组件的依赖关系。

此外,这种模式加强了代码的可测试性,从而使它不容易出错。
在容器中注册服务¶

框架自己或开发人员均可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),能够从容器中请求调用组件B,而不是建立组件B的一个实例。

这种工做方式为咱们提供了许多优势:

咱们能够更换一个组件,从他们自己或者第三方轻松建立。
在组件发布以前,咱们能够充分的控制对象的初始化,并对对象进行各类设置。
咱们可使用统一的方式从组件获得一个结构化的全局实例

服务能够经过如下几种方式注入到容器:

复制代码
 1 <?php
 2 
 3 //Create the Dependency Injector Container
 4 $di = new Phalcon\DI();
 5 
 6 //By its class name
 7 $di->set("request", 'Phalcon\Http\Request');
 8 
 9 //Using an anonymous function, the instance will lazy loaded
10 $di->set("request", function(){
11     return new Phalcon\Http\Request();
12 });
13 
14 //Registering directly an instance
15 $di->set("request", new Phalcon\Http\Request());
16 
17 //Using an array definition
18 $di->set("request", array(
19     "className" => 'Phalcon\Http\Request'
20 ));
复制代码

在上面的例子中,当向框架请求访问一个请求数据时,它将首先肯定容器中是否存在这个”reqeust”名称的服务。

容器会反回一个请求数据的实例,开发人员最终获得他们想要的组件。

在上面示例中的每一种方法都有优缺点,具体使用哪种,由开发过程当中的特定场景来决定的。

用一个字符串来设定一个服务很是简单,但缺乏灵活性。设置服务时,使用数组则提供了更多的灵活性,并且可使用较复杂的代码。lambda函数是二者之间一个很好的平衡,但也可能致使更多的维护管理成本。

Phalcon\DI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,而后存存储到容器中。在容器中,经过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

复制代码
 1 <?php
 2 
 3 //Register a service "db" with a class name and its parameters
 4 $di->set("db", array(
 5     "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
 6     "parameters" => array(
 7           "parameter" => array(
 8                "host" => "localhost",
 9                "username" => "root",
10                "password" => "secret",
11                "dbname" => "blog"
12           )
13     )
14 ));
15 
16 //Using an anonymous function
17 $di->set("db", function(){
18     return new Phalcon\Db\Adapter\Pdo\Mysql(array(
19          "host" => "localhost",
20          "username" => "root",
21          "password" => "secret",
22          "dbname" => "blog"
23     ));
24 });
复制代码

以上这两种服务的注册方式产生相同的结果。而后,经过数组定义的,在后面须要的时候,你能够修改服务参数:

复制代码
1 <?php
2 
3 $di->setParameter("db", 0, array(
4     "host" => "localhost",
5     "username" => "root",
6     "password" => "secret"
7 ));
复制代码

从容器中得到服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:

1 <?php
2      $request = $di->get("request");

或者经过下面这种魔术方法的形式调用:

1 <?php
2 
3 $request = $di->getRequest();
4 
5 Phalcon\DI 同时容许服务重用,为了获得一个已经实例化过的服务,可使用 getShared() 方法的形式来得到服务。

具体的 Phalcon\Http\Request 请求示例:

1 <?php
2 
3 $request = $di->getShared("request");

参数还能够在请求的时候经过将一个数组参数传递给构造函数的方式:

1 <?php
2     $component = $di->get("MyComponent", array("some-parameter", "other"))
相关文章
相关标签/搜索