理解 PHP 依賴註入 | Laravel IoC容器

来源:http://www.cnblogs.com/zyf-zhaoyafei/archive/2016/01/12/5125272.html
-Advertisement-
Play Games

Laravel框架的依賴註入確實很強大,並且通過容器實現依賴註入可以有選擇性的載入需要的服務,提高初始化框架的開銷,下麵是我在網上看到的一個帖子,寫的很好拿來與大家分享,文章從開始按照傳統的類設計資料庫連接一直到通過容器載入服務這個高度解耦的設計展示了依賴註入的強大之處,值得我們借鑒和學習。--.....


  Laravel框架的依賴註入確實很強大,並且通過容器實現依賴註入可以有選擇性的載入需要的服務,減少初始化框架的開銷,下麵是我在網上看到的一個帖子,寫的很好拿來與大家分享,文章從開始按照傳統的類設計資料庫連接一直到通過容器載入服務這個高度解耦的設計展示了依賴註入的強大之處,值得我們借鑒和學習。

-----------------------------------------------------------分割線下麵是大牛的原文----------------------------------------------------------

      原文連接(http://www.yuansir-web.com/2014/03/20)

  首先,我們假設,我們要開發一個組件命名為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();

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

 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());

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

  例如,如果組件中有較多的依賴關係,我們需要創建多個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 
3 $component = $di->get("MyComponent", array("some-parameter", "other"))

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 之前提到了JS中比較簡單的設計模式,在各種設計模式中被最常使用的工具之一就是原型鏈的繼承。作為OOP的特質之一——繼承,今天主要談談JS中比較簡單的繼承方法。 最基礎的原型鏈繼承在這裡就不覆述了,主要講一下其他的繼承模式。1.借用構造函數繼承function Father (name) {...
  • var EventEmitter = require('events').EventEmitter;var emitter = new EventEmitter();console.log(emitter.getMaxListeners());//設置可監聽事件的最大個數emitter.setMax...
  • 效果:http://hovertree.com/texiao/html5/canvas/1/代碼: 1 2 3 4 15 測試fillStyle - 何問起16 17 18 19 20 21 22 何問起23 24 更多:http://www.cnblogs.com/roucheng/p...
  • 在一個頁面放2個懸浮框,懸浮框隨頁面的上下滾動有上下波動的效果,最終固定在同一位置體驗效果:http://hovertree.com/texiao/jsstudy/1/代碼如下:JavScript頁面懸浮框- 何問起何問起 特效原文效果在一個頁面放2個懸浮框,懸浮框隨頁面的上下滾動有上下波動的效果,...
  • 效果體驗:http://hovertree.com/texiao/jsstudy/2/實現思路 1 滑鼠移入標題(這裡是標簽) 創建一個div,div的內容為滑鼠位置的文本 將創建好的div加到文檔中 為提示層設置位置 2 滑鼠移出標題 移除div 3 當滑鼠在標題內移...
  • jQuery升級踩坑大全背景jQuery想必各個web工程師都再熟悉不過了,不過現如今很多網站還採用了很古老的jQuery版本。其實如果早期版本使用不當,可能會有DOMXSS漏洞,非常建議升級到jQuery 1.9.x或以上版本。前段時間我就主導了這件事情,把公司里我們組負責的項目jQuery版本從...
  • 利用矩陣乘法來實現剛體的平面運動,並繪製剛體運動前後的圖形用平面坐標系中的一個閉合圖形來描述剛體,用一個矩陣X來描述它。X的一列表示剛體一個頂點的坐標。為了使圖形閉合,X的最後一列和第一列相同;為了實現剛體的平移運算,給矩陣X添加元素值為1的一行,使矩陣X的形狀為3Xn若有矩陣:可以證明:矩陣Y1是...
  • 在使用SimpleMappingExceptionResolver實現統一異常處理後(參考Spring MVC的異常統一處理方法), 發現出現異常時,log4j無法在控制台輸出錯誤日誌。因此需要自定義一個繼承至SimpleMappingExceptionResolver的 RrtongMappin....
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...