看Laravel的IoC容器文檔只是介紹執行個體,但是沒有說原理,之前用MVC架構都沒有在意這個概念,無意中在phalcon的文檔中看到這個詳細的介紹,感覺豁然開朗,複製粘貼過來,主要是好久沒有寫東西了,現在確實很懶變得。
首先,我們假設,我們要開發一個組件命名為SomeComponent。這個組件中現在將要注入一個資料庫連接。
在這個例子中,資料庫連接在component中被建立,這種方法是不切實際的,這樣做的話,我們將不能改變資料庫連接參數及資料庫類型等一些參數。
<?phpclass SomeComponent{ /** * The instantiation of the connection is hardcoded inside * the component so is difficult to replace it externally * or change its behavior */ public function someDbTask() { $connection = new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); // ... }}$some = new SomeComponent();$some->someDbTask();
為瞭解決上面所說的問題,我們需要在使用前建立一個外部串連,並注入到容器中。就目前而言,這看起來是一個很好的解決方案:
<?phpclass SomeComponent{ protected $_connection; /** * Sets the connection externally */ public function setConnection($connection) { $this->_connection = $connection; } public function someDbTask() { $connection = $this->_connection; // ... }}$some = new SomeComponent();//Create the connection$connection = new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo"));//Inject the connection in the component$some->setConnection($connection);$some->someDbTask();
現在我們來考慮一個問題,我們在應用程式中的不同地方使用此組件,將多次建立資料庫連接。使用一種類似全域註冊表的方式,從這獲得一個資料庫連接執行個體,而不是使用一次就建立一次。
<?phpclass Registry{ /** * Returns the connection */ public static function getConnection() { return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); }}class SomeComponent{ protected $_connection; /** * Sets the connection externally */ public function setConnection($connection){ $this->_connection = $connection; } public function someDbTask() { $connection = $this->_connection; // ... }}$some = new SomeComponent();//Pass the connection defined in the registry$some->setConnection(Registry::getConnection());$some->someDbTask();
現在,讓我們來想像一下,我們必須在組件中實現兩個方法,首先需要建立一個新的資料庫連接,第二個總是獲得一個共用串連:
<?phpclass Registry{ protected static $_connection; /** * Creates a connection */ protected static function _createConnection() { return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" )); } /** * Creates a connection only once and returns it */ public static function getSharedConnection() { if (self::$_connection===null){ $connection = self::_createConnection(); self::$_connection = $connection; } return self::$_connection; } /** * Always returns a new connection */ public static function getNewConnection() { return self::_createConnection(); }}class SomeComponent{ protected $_connection; /** * Sets the connection externally */ public function setConnection($connection){ $this->_connection = $connection; } /** * This method always needs the shared connection */ public function someDbTask() { $connection = $this->_connection; // ... } /** * This method always needs a new connection */ public function someOtherDbTask($connection) { }}$some = new SomeComponent();//This injects the shared connection$some->setConnection(Registry::getSharedConnection());$some->someDbTask();//Here, we always pass a new connection as parameter$some->someOtherDbTask(Registry::getConnection());
到此為止,我們已經看到了如何使用依賴注入解決我們的問題。不是在代碼內部建立依賴關係,而是讓其作為一個參數傳遞,這使得我們的程式更容易維護,降低程式碼的耦合度,實現一種松耦合。但是從長遠來看,這種形式的依賴注入也有一些缺點。
例如,如果組件中有較多的依賴關係,我們需要建立多個setter方法傳遞,或建立建構函式進行傳遞。另外,每次使用組件時,都需要建立相依元件,使代碼維護不太易,我們編寫的代碼可能像這樣:
<?php//Create the dependencies or retrieve them from the registry$connection = new Connection();$session = new Session();$fileSystem = new FileSystem();$filter = new Filter();$selector = new Selector();//Pass them as constructor parameters$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);// ... or using setters$some->setConnection($connection);$some->setSession($session);$some->setFileSystem($fileSystem);$some->setFilter($filter);$some->setSelector($selector);
我想,我們不得不在應用程式的許多地方建立這個對象。如果你不需要依賴的組件後,我們又要去代碼注入部分移除建構函式中的參數或者是setter方法。為瞭解決這個問題,我們再次返回去使用一個全域註冊表來建立組件。但是,在建立對象之前,它增加了一個新的抽象層:
<?phpclass SomeComponent{ // ... /** * Define a factory method to create SomeComponent instances injecting its dependencies */ public static function factory() { $connection = new Connection(); $session = new Session(); $fileSystem = new FileSystem(); $filter = new Filter(); $selector = new Selector(); return new self($connection, $session, $fileSystem, $filter, $selector); }}
這一刻,我們好像回到了問題的開始,我們正在建立組件內部的依賴,我們每次都在修改以及找尋一種解決問題的辦法,但這都不是很好的做法。
一種實用和優雅的來解決這些問題,是使用容器的依賴注入,像我們在前面看到的,容器作為全域註冊表,使用容器的依賴注入做為一種橋樑來解決依賴可以使我們的代碼耦合度更低,很好的降低了組件的複雜性:
<?phpclass SomeComponent{ protected $_di; public function __construct($di) { $this->_di = $di; } public function someDbTask() { // Get the connection service // Always returns a new connection $connection = $this->_di->get('db'); } public function someOtherDbTask() { // Get a shared connection service, // this will return the same connection everytime $connection = $this->_di->getShared('db'); //This method also requires a input filtering service $filter = $this->_db->get('filter'); }}$di = new Phalcon\DI();//Register a "db" service in the container$di->set('db', function(){ return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" ));});//Register a "filter" service in the container$di->set('filter', function(){ return new Filter();});//Register a "session" service in the container$di->set('session', function(){ return new Session();});//Pass the service container as unique parameter$some = new SomeComponent($di);$some->someTask();
現在,該組件只有訪問某種service的時候才需要它,如果它不需要,它甚至不初始化,以節約資源。該組件是高度解耦。他們的行為,或者說他們的任何其他方面都不會影響到組件本身。
我們的實現辦法
Phalcon\DI 是一個實現了服務的依賴注入功能的組件,它本身也是一個容器。
由於Phalcon高度解耦,Phalcon\DI 是架構用來整合其他組件的必不可少的部分,開發人員也可以使用這個組件依賴注入和管理應用程式中不同類檔案的執行個體。
基本上,這個組件實現了 Inversion of Control 模式。基於此,對象不再以建構函式接收參數或者使用setter的方式來實現注入,而是直接請求服務的依賴注入。這就大大降低了整體程式的複雜性,因為只有一個方法用以獲得所需要的一個組件的依賴關係。
此外,這種模式增強了代碼的可測試性,從而使它不容易出錯。
在容器中註冊服務¶
架構本身或開發人員都可以註冊服務。當一個組件A要求調用組件B(或它的類的一個執行個體),可以從容器中請求調用組件B,而不是建立組件B的一個執行個體。
這種工作方式為我們提供了許多優點:
我們可以更換一個組件,從他們本身或者第三方輕鬆建立。
在組件發布之前,我們可以充分的控制對象的初始化,並對對象進行各種設定。
我們可以使用統一的方式從組件得到一個結構化的全域執行個體
服務可以通過以下幾種方式注入到容器:
<?php//Create the Dependency Injector Container$di = new Phalcon\DI();//By its class name$di->set("request", 'Phalcon\Http\Request');//Using an anonymous function, the instance will lazy loaded$di->set("request", function(){ return new Phalcon\Http\Request();});//Registering directly an instance$di->set("request", new Phalcon\Http\Request());//Using an array definition$di->set("request", array( "className" => 'Phalcon\Http\Request'));
在上面的例子中,當向架構請求訪問一個請求資料時,它將首先確定容器中是否存在這個”reqeust”名稱的服務。
容器會反回一個請求資料的執行個體,開發人員最終得到他們想要的組件。
在上面樣本中的每一種方法都有優缺點,具體使用哪一種,由開發過程中的特定情境來決定的。
用一個字串來設定一個服務非常簡單,但缺少靈活性。設定服務時,使用數組則提供了更多的靈活性,而且可以使用較複雜的代碼。lambda函數是兩者之間一個很好的平衡,但也可能導致更多的維護管理成本。
Phalcon\DI 提供服務的消極式載入。除非開發人員在注入服務的時候直接執行個體化一個對象,然後存儲存到容器中。在容器中,通過數組,字串等方式儲存的服務都將被消極式載入,即只有在請求對象的時候才被初始化。
<?php//Register a service "db" with a class name and its parameters$di->set("db", array( "className" => "Phalcon\Db\Adapter\Pdo\Mysql", "parameters" => array( "parameter" => array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "blog" ) )));//Using an anonymous function$di->set("db", function(){ return new Phalcon\Db\Adapter\Pdo\Mysql(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "blog" ));});
以上這兩種服務的註冊方式產生相同的結果。然後,通過數組定義的,在後面需要的時候,你可以修改服務參數:
<?php$di->setParameter("db", 0, array( "host" => "localhost", "username" => "root", "password" => "secret"));
從容器中獲得服務的最簡單方式就是使用”get”方法,它將從容器中返回一個新的執行個體:
<?php $request = $di->get("request");
或者通過下面這種魔術方法的形式調用:
<?php$request = $di->getRequest();
Phalcon\DI 同時允許服務重用,為了得到一個已經執行個體化過的服務,可以使用 getShared() 方法的形式來獲得服務。
具體的 Phalcon\Http\Request 請求樣本:
<?php$request = $di->getShared("request");
參數還可以在請求的時候通過將一個數組參數傳遞給建構函式的方式:
<?php$component = $di->get("MyComponent", array("some-parameter", "other"))
原文連結:http://www.yuansir-web.com/?p=1012&preview=true