关于依赖注入在PHP框架中的应用_第1页
关于依赖注入在PHP框架中的应用_第2页
关于依赖注入在PHP框架中的应用_第3页
关于依赖注入在PHP框架中的应用_第4页
关于依赖注入在PHP框架中的应用_第5页
已阅读5页,还剩30页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、关于依赖注入在PHP框架中的应用现在的 PHP 应用包含了很多对象。有的对象能帮你发送电子邮件,另一个可以帮你把数据持久化到数据库中。在你的应用中,你可能会创建一个管理产品库存的对象,或者是一个处理第三方 API 数据的对象。这篇文章中,我们的关注这件事情:应用做了很多事情,它组织了很多对象来处理每一个任务。在 PHP 的 Symfony 2 框架中,有一个特殊的对象,帮助你实例化、组织和获取应用中的那一系列的对象。它叫 Service Container(服务容器),可以让你标准化和集中化地创建应用中对象。容器让生活简化,它速度很快,包含的架构思想促进代码的重用和解耦。所有 Symfony

2、2 的核心的类都使用容器,容器为框架的速度和可扩展性做了最大的贡献。先来了解下什么是 Service。简单的说,一个 Service 就是任何的可以完成某类“全局”任务的 PHP 对象。一个 Service 是一个 PHP 对象的通用性的术语,这个对象能执行特定的任务,通常被“全局”地使用,比如一个数据库连接的对象,或者一个能发送电子邮件的对象。如果拥有很多松耦合的 Service,我们就说这个应用遵循了 SOA(面向服务的架构)。创建一个 Service 很简单,你只要为那份能完成特定任务的代码写个类,就 OK 了。一般来说,PHP 对象如果要成为 Service,必须要在应用中被全局的使用

3、。比如一个 Mailer Service 被全局的用于发送电子邮件,但是由 Mailer 发送的邮件内容对象(每次的内容都不同)就不是 Service。既然 Service 这么容易创建,那有啥了不起的呢?如果你开始考虑将应用中的每个功能都分离开来,你就能开始感受 Service 的好处了。因为每个 Service 只做一个工作,你在任何地方都可以轻松地获得并使用它们的功能。每个 Service 也能更容易的被测试和配置,因为在应用中它们是互相分离的。将你的应用组织成一系列独立的 Service 的类,也是面向对象编程的最佳实践之一。这种技能在任何开发语言中都是好程序员的标志。什么是 Serv

4、ice Container。Service Container 也叫 Dependency Injection Container(依赖注入容器),就是一个简单的 PHP 对象,管理着 Service 们的实例化。假设你有个发送电子邮件的 PHP 类。如果不用 Service Container,在你需要它时,都必须手工地创建对象。这也算简单。但是,如果你不想重复地去配置它,就可以把它作为 Service。当你需要创建一个 Service,它依赖了 Service Container 中一个或几个其他的 Service 们时,你才会意识到容器的强大。假设你的一个新的 Service,依赖了发送

5、电子邮件的 Service。只要在新的 Service 配置中将发送电子邮件的 Service 设为参数即可,如果你的这个 Service 后来做了改动,需要再依赖一个 Service,只需要改下配置,增加参数即可。对应到依赖注入模式,其实 Service Container 就是注入器;Service A 依赖 Service B,前者是依赖者,后者是被依赖者;被依赖者的接口一般就是依赖的定义。这次设计模式解决的是整个框架的架构问题,解决了:功能间的松耦合、框架的扩展性,运行效率也高。其实还是蛮羡慕学 Java 的同学,很早就接触一些好的设计和应用,比如:Spring 框架。当然现在 PHP

6、 的新的框架层出不穷,也借鉴各种好的思想。面包和牛奶已经有了,可以吃了。看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。php view plaincopy1. <?p

7、hp  2.   3. class SomeComponent  4.   5.   6.     /* 7.      * The instantiation of the connection is hardcoded inside 8.     

8、0;* the component so is difficult to replace it externally 9.      * or change its behavior 10.      */  11.     public function someDb

9、Task()  12.       13.         $connection = new Connection(array(  14.             "host" => "localhost&quo

10、t;,  15.             "username" => "root",  16.             "password" => "secret",  

11、;17.             "dbname" => "invo"  18.         );  19.   20.         / .  21. &#

12、160;     22.   23.   24.   25. $some = new SomeComponent();  26. $some->someDbTask();  为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:php view plaincopy1. <?php  2.   3

13、. class SomeComponent  4.   5.   6.     protected $_connection;  7.   8.     /* 9.      * Sets the connection externally 10.    &#

14、160; */  11.     public function setConnection($connection)  12.       13.         $this->_connection = $connection;  14.      

15、 15.   16.     public function someDbTask()  17.       18.         $connection = $this->_connection;  19.   20.      

16、   / .  21.       22.   23.   24.   25. $some = new SomeComponent();  26.   27. /Create the connection  28. $connection = new Connection(arr

17、ay(  29.     "host" => "localhost",  30.     "username" => "root",  31.     "password" => "secret",  

18、32.     "dbname" => "invo"  33. );  34.   35. /Inject the connection in the component  36. $some->setConnection($connection);  37.   38. $some->someDbTask()

19、;  现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。php view plaincopy1. <?php  2.   3. class Registry  4.   5.   6.     /* 7.      * Ret

20、urns the connection 8.      */  9.     public static function getConnection()  10.       11.        return new Connection(array(&#

21、160; 12.             "host" => "localhost",  13.             "username" => "root",  14. &

22、#160;           "password" => "secret",  15.             "dbname" => "invo"  16.    

23、     );  17.       18.   19.   20.   21. class SomeComponent  22.   23.   24.     protected $_connection;  25.   26.  &#

24、160;  /* 27.      * Sets the connection externally 28.      */  29.     public function setConnection($connection)  30.       

25、0; $this->_connection = $connection;  31.       32.   33.     public function someDbTask()  34.       35.         $connect

26、ion = $this->_connection;  36.   37.         / .  38.       39.   40.   41.   42. $some = new SomeComponent();  43.   

27、;44. /Pass the connection defined in the registry  45. $some->setConnection(Registry:getConnection();  46.   47. $some->someDbTask();  现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:php view plaincopy1. <?p

28、hp  2.   3. class Registry  4.   5.   6.     protected static $_connection;  7.   8.     /* 9.      * Creates a connection 10

29、.      */  11.     protected static function _createConnection()  12.       13.         return new Connection(array(  14.  &#

30、160;          "host" => "localhost",  15.             "username" => "root",  16.     

31、;        "password" => "secret",  17.             "dbname" => "invo"  18.       

32、0; );  19.       20.   21.     /* 22.      * Creates a connection only once and returns it 23.      */  24.  &

33、#160;  public static function getSharedConnection()  25.       26.         if (self:$_connection=null)  27.             

34、$connection = self:_createConnection();  28.             self:$_connection = $connection;  29.           30.        

35、; return self:$_connection;  31.       32.   33.     /* 34.      * Always returns a new connection 35.      */  36.  

36、60;  public static function getNewConnection()  37.       38.         return self:_createConnection();  39.       40.   41.   42.

37、  43. class SomeComponent  44.   45.   46.     protected $_connection;  47.   48.     /* 49.      * Sets the connection externally 50.

38、     */  51.     public function setConnection($connection)  52.         $this->_connection = $connection;  53.       54.  

39、0;55.     /* 56.      * This method always needs the shared connection 57.      */  58.     public function someDbTask()  59.  

40、60;    60.         $connection = $this->_connection;  61.   62.         / .  63.       64.   65.   

41、60; /* 66.      * This method always needs a new connection 67.      */  68.     public function someOtherDbTask($connection)  69.    &

42、#160;  70.   71.       72.   73.   74.   75. $some = new SomeComponent();  76.   77. /This injects the shared connection  78. $some->setConnection(Regis

43、try:getSharedConnection();  79.   80. $some->someDbTask();  81.   82. /Here, we always pass a new connection as parameter  83. $some->someOtherDbTask(Registry:getConnection();  到此为止,我们已经看到了如何使用依

44、赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:php view plaincopy1. <?php  2.   3. /Create the dependencies or re

45、trieve them from the registry  4. $connection = new Connection();  5. $session = new Session();  6. $fileSystem = new FileSystem();  7. $filter = new Filter();  8. $selec

46、tor = new Selector();  9.   10. /Pass them as constructor parameters  11. $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);  12.   13. / .

47、0;or using setters  14.   15. $some->setConnection($connection);  16. $some->setSession($session);  17. $some->setFileSystem($fileSystem);  18. $some->setFilter($filter);  19. $some->setSelector($selector); 

48、60;我想,我们不得不在应用程序的许多地方创建这个对象。如果你不需要依赖的组件后,我们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:php view plaincopy1. <?php  2.   3. class SomeComponent  4.   5.   6.     / . &

49、#160;7.   8.     /* 9.      * Define a factory method to create SomeComponent instances injecting its dependencies 10.      */  11.   &

50、#160; public static function factory()  12.       13.   14.         $connection = new Connection();  15.         $session

51、0;= new Session();  16.         $fileSystem = new FileSystem();  17.         $filter = new Filter();  18.        &#

52、160;$selector = new Selector();  19.   20.         return new self($connection, $session, $fileSystem, $filter, $selector);  21.       22.   23.

53、  这一刻,我们好像回到了问题的开始,我们正在创建组件内部的依赖,我们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的做法。一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像我们在前面看到的,容器作为全局注册表,使用容器的依赖注入做为一种桥梁来解决依赖可以使我们的代码耦合度更低,很好的降低了组件的复杂性:php view plaincopy1. <?php  2.   3. class SomeComponent  4.   5.   

54、6.     protected $_di;  7.   8.     public function _construct($di)  9.       10.         $this->_di = $di;  11.  

55、60;    12.   13.     public function someDbTask()  14.       15.   16.         / Get the connection service  17.  &

56、#160;      / Always returns a new connection  18.         $connection = $this->_di->get('db');  19.   20.       21.  

57、; 22.     public function someOtherDbTask()  23.       24.   25.         / Get a shared connection service,  26.     

58、60;   / this will return the same connection everytime  27.         $connection = $this->_di->getShared('db');  28.   29.      

59、0;  /This method also requires a input filtering service  30.         $filter = $this->_db->get('filter');  31.   32.       33. 

60、60; 34.   35.   36. $di = new PhalconDI();  37.   38. /Register a "db" service in the container  39. $di->set('db', function()  40.     return&#

61、160;new Connection(array(  41.         "host" => "localhost",  42.         "username" => "root",  43.    

62、;     "password" => "secret",  44.         "dbname" => "invo"  45.     );  46. );  47.   48. /Regis

63、ter a "filter" service in the container  49. $di->set('filter', function()  50.     return new Filter();  51. );  52.   53. /Register a "session"

64、; service in the container  54. $di->set('session', function()  55.     return new Session();  56. );  57.   58. /Pass the service container as unique par

65、ameter  59. $some = new SomeComponent($di);  60.   61. $some->someTask();  现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。我们的实现办法PhalconDI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。由于Phalcon高度解耦,PhalconDI 是框架用来集成其他组件

66、的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。此外,这种模式增强了代码的可测试性,从而使它不容易出错。在容器中注册服务¶框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。这种工作方式为我们提供了许多优点:

67、我们可以更换一个组件,从他们本身或者第三方轻松创建。在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。我们可以使用统一的方式从组件得到一个结构化的全局实例服务可以通过以下几种方式注入到容器:php view plaincopy1. <?php  2.   3. /Create the Dependency Injector Container  4. $di = new PhalconDI();  5. 

68、60; 6. /By its class name  7. $di->set("request", 'PhalconHttpRequest');  8.   9. /Using an anonymous function, the instance will lazy loaded  10. $di->set("request&q

69、uot;, function()  11.     return new PhalconHttpRequest();  12. );  13.   14. /Registering directly an instance  15. $di->set("request", new PhalconHttpRequest();  16.

70、   17. /Using an array definition  18. $di->set("request", array(  19.     "className" => 'PhalconHttpRequest'  20. );  在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeus

71、t”名称的服务。容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。PhalconDI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。php view plaincopy

72、1. <?php  2.   3. /Register a service "db" with a class name and its parameters  4. $di->set("db", array(  5.     "className" => "P

73、halconDbAdapterPdoMysql",  6.     "parameters" => array(  7.           "parameter" => array(  8.         

74、0;      "host" => "localhost",  9.                "username" => "root",  10.      &#

75、160;         "password" => "secret",  11.                "dbname" => "blog"  12.   &

76、#160;       )  13.     )  14. );  15.   16. /Using an anonymous function  17. $di->set("db", function()  18.     return new

77、0;PhalconDbAdapterPdoMysql(array(  19.          "host" => "localhost",  20.          "username" => "root",  21. &#

78、160;        "password" => "secret",  22.          "dbname" => "blog"  23.     );  24. );  

79、;以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:php view plaincopy1. <?php  2.   3. $di->setParameter("db", 0, array(  4.     "host" => "localhost",  5.    &#

80、160;"username" => "root",  6.     "password" => "secret"  7. );  从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:php view plaincopy1. <?php   2. $request = $d

81、i->get("request");  或者通过下面这种魔术方法的形式调用:php view plaincopy1. <?php  2.   3. $request = $di->getRequest();  PhalconDI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。具体的 PhalconHttpRequest 请求示例:php view plaincopy1. <

82、?php  2.   3. $request = $di->getShared("request");  参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:php view plaincopy1. <?php  2.   3. $component = $di->get("MyComponent", array("some-parameter",&

83、#160;"other")  首先先别追究这个设计模式的定义,否则你一定会被说的云里雾里,笔者就是深受其害,百度了N多文章,都是从理论角度来描述,充斥着大量的生涩词汇,要么就是java代码描述的,也生涩。不管怎么样,总算弄清楚一些了,下面就以php的角度来描述一下依赖注入这个概念。先假设我们这里有一个类,类里面需要用到数据库连接,按照最最原始的办法,我们可能是这样写这个类的:1. class example 2.     3.     private

84、60;$_db;4.5.     function _construct()6.         include "./Lib/Db.php"7.         $this->_db = new Db("localhost","root","123456",&q

85、uot;test");8.     9.10.     function getList()11.         $this->_db->query(".");/这里具体sql语句就省略不写了12.     13.  复制代码过程:在构造函数里先将数据库类文件include进来;然后又通过new Db并传入数据库连接信息实例化db类;

86、之后getList方法就可以通过$this->_db来调用数据库类,实现数据库操作。看上去我们实现了想要的功能,但是这是一个噩梦的开始,以后example1,example2,example3.越来越多的类需要用到db组件,如果都这么写的话,万一有一天数据库密码改了或者db类发生变化了,岂不是要回头修改所有类文件?ok,为了解决这个问题,工厂模式出现了,我们创建了一个Factory方法,并通过Factory:getDb()方法来获得db组件的实例:1. class Factory 2.     public stati

87、c function getDb()3.         include "./Lib/Db.php"4.         return new Db("localhost","root","123456","test");5.     6. 

88、60;复制代码sample类变成:1. class example 2.     3.     private $_db;4.5.     function _construct()6.         $this->_db = Factory:getDb();7.     8.9. 

89、0;   function getList()10.         $this->_db->query(".");/这里具体sql语句就省略不写了11.     12.  复制代码这样就完美了吗?再次想想一下以后example1,example2,example3.所有的类,你都需要在构造函数里通过Factory:getDb();获的一个Db实例,实际上你由原来的直接与Db类的耦合变为了和Fa

90、ctory工厂类的耦合,工厂类只是帮你把数据库连接信息给包装起来了,虽然当数据库信息发生变化时只要修改Factory:getDb()方法就可以了,但是突然有一天工厂方法需要改名,或者getDb方法需要改名,你又怎么办?当然这种需求其实还是很操蛋的,但有时候确实存在这种情况,一种解决方式是:我们不从example类内部实例化Db组件,我们依靠从外部的注入,什么意思呢?看下面的例子:1. class example 2.     private $_db;3.     function&#

91、160;getList()4.         $this->_db->query(".");/这里具体sql语句就省略不写了5.     6.     /从外部注入db连接7.     function setDb($connection)8.         $this-&

92、gt;_db = $connection;9.     10.  11.12.  /调用13. $example = new example();14. $example->setDb(Factory:getDb();/注入db连接15. $example->getList();复制代码这样一来,example类完全与外部类解除耦合了,你可以看到Db类里面已经没有工厂方法或Db类的身影了。我们通过从外部调用example类的setDb方法,将连接实例直接注入进去。这样exa

93、mple完全不用关心db连接怎么生成的了。这就叫依赖注入,实现不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。这还没完,我们再假设example类里面除了db还要用到其他外部类,我们通过:1. $example->setDb(Factory:getDb();/注入db连接2. $example->setFile(Factory:getFile();/注入文件处理类3. $example->setImage(Factory:getImage();/注入Image处理类4.  .复制代码我们没完没了

94、的写这么多set?累不累?ok,为了不用每次写这么多行代码,我们又去弄了一个工厂方法:1. class Factory 2.     public static function getExample()3.         $example = new example();4.         $example-&g

95、t;setDb(Factory:getDb();/注入db连接5.         $example->setFile(Factory:getFile();/注入文件处理类6.         $example->setImage(Factory:getImage();/注入Image处理类7.         return $expa

96、mple;8.     9.10.  复制代码实例化example时变为:1. $example=Factory:getExample();2. $example->getList();复制代码似乎完美了,但是怎么感觉又回到了上面第一次用工厂方法时的场景?这确实不是一个好的解决方案,所以又提出了一个概念:容器,又叫做IoC容器、DI容器。我们本来是通过setXXX方法注入各种类,代码很长,方法很多,虽然可以通过一个工厂方法包装,但是还不是那么爽,好吧,我们不用setXXX方法了,这样也就不用工厂方法二次包装了,那么我们还怎么实现依赖注入呢?这里我们引入一个约定:在example类的构造函数里传入一个名为Di $di的参数,如下:1. class example 2.     private $_di;3.     fu

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

最新文档

评论

0/150

提交评论