關于依賴注入在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

提交評論