php 依賴注入容器


原文: http://blog.csdn.net/realghost/article/details/35212285

https://my.oschina.net/cxz001/blog/533166

http://www.oschina.net/code/snippet_1171845_48046

http://blog.csdn.net/wzllai/article/details/24485245

看Laravel的IoC容器文檔只是介紹實例,但是沒有說原理,之前用MVC框架都沒有在意這個概念,無意中在phalcon的文檔中看到這個詳細的介紹,感覺豁然開朗,復制粘貼過來,主要是好久沒有寫東西了,現在確實很懶變得!

首先,我們假設,我們要開發一個組件命名為SomeComponent。這個組件中現在將要注入一個數據庫連接。

在這個例子中,數據庫連接在component中被創建,這種方法是不切實際的,這樣做的話,我們將不能改變數據庫連接參數及數據庫類型等一些參數。

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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();  

 

為了解決上面所說的問題,我們需要在使用前創建一個外部連接,並注入到容器中。就目前而言,這看起來是一個很好的解決方案:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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();  


現在我們來考慮一個問題,我們在應用程序中的不同地方使用此組件,將多次創建數據庫連接。使用一種類似全局注冊表的方式,從這獲得一個數據庫連接實例,而不是使用一次就創建一次。

 

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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();  


現在,讓我們來想像一下,我們必須在組件中實現兩個方法,首先需要創建一個新的數據庫連接,第二個總是獲得一個共享連接:

 

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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());  


到此為止,我們已經看到了如何使用依賴注入解決我們的問題。不是在代碼內部創建依賴關系,而是讓其作為一個參數傳遞,這使得我們的程序更容易維護,降低程序代碼的耦合度,實現一種松耦合。但是從長遠來看,這種形式的依賴注入也有一些缺點。

 

例如,如果組件中有較多的依賴關系,我們需要創建多個setter方法傳遞,或創建構造函數進行傳遞。另外,每次使用組件時,都需要創建依賴組件,使代碼維護不太易,我們編寫的代碼可能像這樣:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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方法。為了解決這個問題,我們再次返回去使用一個全局注冊表來創建組件。但是,在創建對象之前,它增加了一個新的抽象層:

 

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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. }  

 

 

這一刻,我們好像回到了問題的開始,我們正在創建組件內部的依賴,我們每次都在修改以及找尋一種解決問題的辦法,但這都不是很好的做法。

一種實用和優雅的來解決這些問題,是使用容器的依賴注入,像我們在前面看到的,容器作為全局注冊表,使用容器的依賴注入做為一種橋梁來解決依賴可以使我們的代碼耦合度更低,很好的降低了組件的復雜性:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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的一個實例。

這種工作方式為我們提供了許多優點:

我們可以更換一個組件,從他們本身或者第三方輕松創建。
在組件發布之前,我們可以充分的控制對象的初始化,並對對象進行各種設置。
我們可以使用統一的方式從組件得到一個結構化的全局實例

服務可以通過以下幾種方式注入到容器:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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 提供服務的延遲加載。除非開發人員在注入服務的時候直接實例化一個對象,然后存存儲到容器中。在容器中,通過數組,字符串等方式存儲的服務都將被延遲加載,即只有在請求對象的時候才被初始化。

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  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. });  


以上這兩種服務的注冊方式產生相同的結果。然后,通過數組定義的,在后面需要的時候,你可以修改服務參數:

 

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2.   
  3. $di->setParameter("db", 0, array(  
  4.     "host" => "localhost",  
  5.     "username" => "root",  
  6.     "password" => "secret"  
  7. ));  

 

從容器中獲得服務的最簡單方式就是使用”get”方法,它將從容器中返回一個新的實例:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <?php   
  2. $request = $di->get("request");  

 

或者通過下面這種魔術方法的形式調用:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2.   
  3. $request = $di->getRequest();  

 

Phalcon\DI 同時允許服務重用,為了得到一個已經實例化過的服務,可以使用 getShared() 方法的形式來獲得服務。

具體的 Phalcon\Http\Request 請求示例:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
  1. <?php  
  2.   
  3. $request = $di->getShared("request");  

 

參數還可以在請求的時候通過將一個數組參數傳遞給構造函數的方式:

 

[php]  view plain  copy
 
 在CODE上查看代碼片派生到我的代碼片
    1. <?php  
    2.   
    3. $component = $di->get("MyComponent", array("some-parameter", "other"))  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM