看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!

首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。

在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。

01 <?php
02  
03 class SomeComponent
04 {
05  
06     /**
07      * The instantiation of the connection is hardcoded inside
08      * the component so is difficult to replace it externally
09      * 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();

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

01 <?php
02  
03 class SomeComponent
04 {
05  
06     protected $_connection;
07  
08     /**
09      * 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();

现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。

01 <?php
02  
03 class Registry
04 {
05  
06     /**
07      * Returns the connection
08      */
09     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();

现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:

01 <?php
02  
03 class Registry
04 {
05  
06     protected static $_connection;
07  
08     /**
09      * 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());

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

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

01 <?php
02  
03 //Create the dependencies or retrieve them from the registry
04 $connection = new Connection();
05 $session = new Session();
06 $fileSystem = new FileSystem();
07 $filter = new Filter();
08 $selector = new Selector();
09  
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方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:

01 <?php
02  
03 class SomeComponent
04 {
05  
06     // ...
07  
08     /**
09      * 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 }

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

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

01 <?php
02  
03 class SomeComponent
04 {
05  
06     protected $_di;
07  
08     public function __construct($di)
09     {
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的一个实例。

这种工作方式为我们提供了许多优点:

我们可以更换一个组件,从他们本身或者第三方轻松创建。
在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。
我们可以使用统一的方式从组件得到一个结构化的全局实例

服务可以通过以下几种方式注入到容器:

01 <?php
02  
03 //Create the Dependency Injector Container
04 $di = new Phalcon\DI();
05  
06 //By its class name
07 $di->set("request", 'Phalcon\Http\Request');
08  
09 //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 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。

01 <?php
02  
03 //Register a service "db" with a class name and its parameters
04 $di->set("db", array(
05     "className" => "Phalcon\Db\Adapter\Pdo\Mysql",
06     "parameters" => array(
07           "parameter" => array(
08                "host" => "localhost",
09                "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 $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  
3 $component = $di->get("MyComponent", array("some-parameter", "other"))

理解PHP 依赖注入|Laravel IoC容器的更多相关文章

  1. 【转】理解 PHP 依赖注入 | Laravel IoC容器

    Laravel框架的依赖注入确实很强大,并且通过容器实现依赖注入可以有选择性的加载需要的服务,减少初始化框架的开销,下面是我在网上看到的一个帖子,写的很好拿来与大家分享,文章从开始按照传统的类设计数据 ...

  2. C#中的依赖注入和IoC容器

    在本文中,我们将通过用C#重构一个非常简单的代码示例来解释依赖注入和IoC容器. 简介: 依赖注入和IoC乍一看可能相当复杂,但它们非常容易学习和理解. 在本文中,我们将通过在C#中重构一个非常简单的 ...

  3. SpringIOC的概念理解、构造器注入、setter注入、p命名空间注入、IOC容器介绍与比较

    1.IOC概念理解 IOC(Inversion of Control)即“控制反转”,不是什么技术,而是一种设计思想.在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象 ...

  4. 理解 PHP 依赖注入 和 控制反转

    理解 PHP 依赖注入 和 控制反转   要想理解 PHP 依赖注入 和 控制反转 两个概念,就必须搞清楚如下的两个问题: DI -- Dependency Injection 依赖注入 IoC -- ...

  5. Atitit js中的依赖注入di ioc的实现

    Atitit js中的依赖注入di ioc的实现 全类名(FQCN)为标识符1 混合请求模式1 使用类内  builder  即可..2 Service locator method走ok拦2 Jav ...

  6. spring 依赖注入(IOC DI)

    依赖注入(IOC DI) 依赖注入的两种方式: 1. set注入 Spring要求使用set注入方式的时候,Bean需要提供一个无参数的构造方法.并提供一个属性的setter方法.例如: packag ...

  7. [ASP.NET Core 3框架揭秘] 依赖注入:IoC模式

    原文:[ASP.NET Core 3框架揭秘] 依赖注入:IoC模式 正如我们在<依赖注入:控制反转>提到过的,很多人将IoC理解为一种“面向对象的设计模式”,实际上IoC不仅与面向对象没 ...

  8. 依赖注入和IOC

    http://www.bbsmvc.com/archiver/csharp/thread-831-1.html 本来想使用一下Ninject的,然后搜索了很久,都没找到比较详细的关于Ninject的使 ...

  9. 依赖注入(IOC)二

    依赖注入(IOC)二 上一章我们讲了构造注入与设值注入,这一篇我们主要讲接口注入与特性注入. 接口注入 接口注入是将抽象类型的入口以方法定义在一个接口中,如果客户类型需要获得这个方法,就需要以实现这个 ...

随机推荐

  1. 剑指Offer06 旋转数组的最小值

    /************************************************************************* > File Name: 06_MinNum ...

  2. BZOJ 1827: [Usaco2010 Mar]gather 奶牛大集会 树形DP

    [Usaco2010 Mar]gather 奶牛大集会 Bessie正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会.当然,她会选择最方便的地点来举办这次集会.每个奶牛居住在 N(1 ...

  3. 真机调试时提示: could not change executable permissions on the application

    原因:同一个bundle identifier不能重复在两个app中使用: 解决:卸载掉手机上的另一个app即可重新安装.

  4. JAVA IO流的简单总结+收集日志异常信息

    1.字节流: IuputStream 所有字节输出流的超类 . 抽象类 ---- FileInputStream ---- BufferedInputStream:提供数据的读取效率,拓展方法(内部维 ...

  5. 网站seo新手快速提升自己的技巧

    第一.找自身的问题 大多数从业者都有下面两个严重的问题: 1.过于放大SEO的重要性每个人,都有自大的习惯,地位越NB往往越把自己认知的一切当做真理,其实有可能那只是井口那巴掌大的一片天.在网络营销中 ...

  6. Oracle在所有内容前追加一些内容的方法

     参照下面的sql语句. SQL> SELECT * FROM UserInfo; NAME                    CHINESE -------------------- ...

  7. cocos2dx屏幕适配方案

    我们在利用cocos2dx来开发游戏时,在开始时就不可避免的会遇到屏幕适配问题,来使我们的游戏适应移动终端的各种分辨率大小.目前,大家采用的屏幕适配方案不一,网上的资料也比较丰富,下面我也将自己使用的 ...

  8. 实例介绍Cocos2d-x开关菜单

    开关菜单是MenuItemToggle类实现的,它是一种可以进行两种状态切换的菜单.它可以通过下面的函数创建: static MenuItemToggle*createWithCallback  ( ...

  9. 【学习笔记】【C语言】流程控制

    顺序结构:默认的流程结构.按照书写顺序执行每一条语句. 选择结构:对给定的条件进行判断,再根据判断结果来决定执行哪一段代码. 循环结构:在给定条件成立的情况下,反复执行某一段代码.

  10. C#中多线程的简单应用

    下面是C#中使用多线程的一个简单用法介绍: //主线程: Thread thread = new Thread(new ThreadStart(ReadExportData));//创建分支线程thr ...