對(duì)PHP依賴注入的理解實(shí)例分析
本文實(shí)例講述了對(duì)PHP依賴注入的理解。分享給大家供大家參考,具體如下:
看Laravel的IoC容器文檔只是介紹實(shí)例,但是沒有說原理,之前用MVC框架都沒有在意這個(gè)概念,無意中在phalcon的文檔中看到這個(gè)詳細(xì)的介紹,感覺豁然開朗,復(fù)制粘貼過來,主要是好久沒有寫東西了,現(xiàn)在確實(shí)很懶變得!
首先,我們假設(shè),我們要開發(fā)一個(gè)組件命名為SomeComponent。這個(gè)組件中現(xiàn)在將要注入一個(gè)數(shù)據(jù)庫(kù)連接。
在這個(gè)例子中,數(shù)據(jù)庫(kù)連接在component中被創(chuàng)建,這種方法是不切實(shí)際的,這樣做的話,我們將不能改變數(shù)據(jù)庫(kù)連接參數(shù)及數(shù)據(jù)庫(kù)類型等一些參數(shù)。
class 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();
為了解決上面所說的問題,我們需要在使用前創(chuàng)建一個(gè)外部連接,并注入到容器中。就目前而言,這看起來是一個(gè)很好的解決方案:
class 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();
現(xiàn)在我們來考慮一個(gè)問題,我們?cè)趹?yīng)用程序中的不同地方使用此組件,將多次創(chuàng)建數(shù)據(jù)庫(kù)連接。使用一種類似全局注冊(cè)表的方式,從這獲得一個(gè)數(shù)據(jù)庫(kù)連接實(shí)例,而不是使用一次就創(chuàng)建一次。
class 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();
現(xiàn)在,讓我們來想像一下,我們必須在組件中實(shí)現(xiàn)兩個(gè)方法,首先需要?jiǎng)?chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)連接,第二個(gè)總是獲得一個(gè)共享連接:
class 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());
到此為止,我們已經(jīng)看到了如何使用依賴注入解決我們的問題。不是在代碼內(nèi)部創(chuàng)建依賴關(guān)系,而是讓其作為一個(gè)參數(shù)傳遞,這使得我們的程序更容易維護(hù),降低程序代碼的耦合度,實(shí)現(xiàn)一種松耦合。但是從長(zhǎng)遠(yuǎn)來看,這種形式的依賴注入也有一些缺點(diǎn)。
例如,如果組件中有較多的依賴關(guān)系,我們需要?jiǎng)?chuàng)建多個(gè)setter方法傳遞,或創(chuàng)建構(gòu)造函數(shù)進(jìn)行傳遞。另外,每次使用組件時(shí),都需要?jiǎng)?chuàng)建依賴組件,使代碼維護(hù)不太易,我們編寫的代碼可能像這樣:
//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);
我想,我們不得不在應(yīng)用程序的許多地方創(chuàng)建這個(gè)對(duì)象。如果你不需要依賴的組件后,我們又要去代碼注入部分移除構(gòu)造函數(shù)中的參數(shù)或者是setter方法。為了解決這個(gè)問題,我們?cè)俅畏祷厝ナ褂靡粋€(gè)全局注冊(cè)表來創(chuàng)建組件。但是,在創(chuàng)建對(duì)象之前,它增加了一個(gè)新的抽象層:
class 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); } }
這一刻,我們好像回到了問題的開始,我們正在創(chuàng)建組件內(nèi)部的依賴,我們每次都在修改以及找尋一種解決問題的辦法,但這都不是很好的做法。
一種實(shí)用和優(yōu)雅的來解決這些問題,是使用容器的依賴注入,像我們?cè)谇懊婵吹降模萜髯鳛槿肿?cè)表,使用容器的依賴注入做為一種橋梁來解決依賴可以使我們的代碼耦合度更低,很好的降低了組件的復(fù)雜性:
class 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();
現(xiàn)在,該組件只有訪問某種service的時(shí)候才需要它,如果它不需要,它甚至不初始化,以節(jié)約資源。該組件是高度解耦。他們的行為,或者說他們的任何其他方面都不會(huì)影響到組件本身。我們的實(shí)現(xiàn)辦法
Phalcon\DI 是一個(gè)實(shí)現(xiàn)了服務(wù)的依賴注入功能的組件,它本身也是一個(gè)容器。
由于Phalcon高度解耦,Phalcon\DI 是框架用來集成其他組件的必不可少的部分,開發(fā)人員也可以使用這個(gè)組件依賴注入和管理應(yīng)用程序中不同類文件的實(shí)例。
基本上,這個(gè)組件實(shí)現(xiàn)了 Inversion of Control 模式。基于此,對(duì)象不再以構(gòu)造函數(shù)接收參數(shù)或者使用setter的方式來實(shí)現(xiàn)注入,而是直接請(qǐng)求服務(wù)的依賴注入。這就大大降低了整體程序的復(fù)雜性,因?yàn)橹挥幸粋€(gè)方法用以獲得所需要的一個(gè)組件的依賴關(guān)系。
此外,這種模式增強(qiáng)了代碼的可測(cè)試性,從而使它不容易出錯(cuò)。
在容器中注冊(cè)服務(wù)
框架本身或開發(fā)人員都可以注冊(cè)服務(wù)。當(dāng)一個(gè)組件A要求調(diào)用組件B(或它的類的一個(gè)實(shí)例),可以從容器中請(qǐng)求調(diào)用組件B,而不是創(chuàng)建組件B的一個(gè)實(shí)例。
這種工作方式為我們提供了許多優(yōu)點(diǎn):
我們可以更換一個(gè)組件,從他們本身或者第三方輕松創(chuàng)建。
在組件發(fā)布之前,我們可以充分的控制對(duì)象的初始化,并對(duì)對(duì)象進(jìn)行各種設(shè)置。
我們可以使用統(tǒng)一的方式從組件得到一個(gè)結(jié)構(gòu)化的全局實(shí)例
服務(wù)可以通過以下幾種方式注入到容器:
//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' ));
在上面的例子中,當(dāng)向框架請(qǐng)求訪問一個(gè)請(qǐng)求數(shù)據(jù)時(shí),它將首先確定容器中是否存在這個(gè)”reqeust”名稱的服務(wù)。
容器會(huì)反回一個(gè)請(qǐng)求數(shù)據(jù)的實(shí)例,開發(fā)人員最終得到他們想要的組件。
在上面示例中的每一種方法都有優(yōu)缺點(diǎn),具體使用哪一種,由開發(fā)過程中的特定場(chǎng)景來決定的。
用一個(gè)字符串來設(shè)定一個(gè)服務(wù)非常簡(jiǎn)單,但缺少靈活性。設(shè)置服務(wù)時(shí),使用數(shù)組則提供了更多的靈活性,而且可以使用較復(fù)雜的代碼。lambda函數(shù)是兩者之間一個(gè)很好的平衡,但也可能導(dǎo)致更多的維護(hù)管理成本。
Phalcon\DI 提供服務(wù)的延遲加載。除非開發(fā)人員在注入服務(wù)的時(shí)候直接實(shí)例化一個(gè)對(duì)象,然后存存儲(chǔ)到容器中。在容器中,通過數(shù)組,字符串等方式存儲(chǔ)的服務(wù)都將被延遲加載,即只有在請(qǐng)求對(duì)象的時(shí)候才被初始化。
//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" )); });
以上這兩種服務(wù)的注冊(cè)方式產(chǎn)生相同的結(jié)果。然后,通過數(shù)組定義的,在后面需要的時(shí)候,你可以修改服務(wù)參數(shù):
$di->setParameter("db", 0, array( "host" => "localhost", "username" => "root", "password" => "secret" ));
從容器中獲得服務(wù)的最簡(jiǎn)單方式就是使用”get”方法,它將從容器中返回一個(gè)新的實(shí)例:
$request = $di->get("request");
或者通過下面這種魔術(shù)方法的形式調(diào)用:
$request = $di->getRequest();
Phalcon\DI 同時(shí)允許服務(wù)重用,為了得到一個(gè)已經(jīng)實(shí)例化過的服務(wù),可以使用 getShared() 方法的形式來獲得服務(wù)。
具體的 Phalcon\Http\Request 請(qǐng)求示例:
$request = $di->getShared("request");
參數(shù)還可以在請(qǐng)求的時(shí)候通過將一個(gè)數(shù)組參數(shù)傳遞給構(gòu)造函數(shù)的方式:
$component = $di->get("MyComponent", array("some-parameter", "other"));
更多關(guān)于PHP相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《php面向?qū)ο蟪绦蛟O(shè)計(jì)入門教程》、《PHP基本語法入門教程》、《PHP運(yùn)算與運(yùn)算符用法總結(jié)》、《PHP網(wǎng)絡(luò)編程技巧總結(jié)》、《PHP數(shù)組(Array)操作技巧大全》、《php字符串(string)用法總結(jié)》、《php+mysql數(shù)據(jù)庫(kù)操作入門教程》及《php常見數(shù)據(jù)庫(kù)操作技巧匯總》
希望本文所述對(duì)大家PHP程序設(shè)計(jì)有所幫助。
相關(guān)文章
php實(shí)現(xiàn)的返回?cái)?shù)據(jù)格式化類實(shí)例
這篇文章主要介紹了php實(shí)現(xiàn)的返回?cái)?shù)據(jù)格式化類及其應(yīng)用實(shí)例,包括針對(duì)XML、JSON等的格式化,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-09-09phplock(php進(jìn)程鎖) v1.0 beta1
PHP在多進(jìn)程模式下(并發(fā)的web訪問)由于沒有內(nèi)置的鎖支持,在處理一些資源的之后,很容易出現(xiàn)并發(fā)性問題。2009-11-11php+mysqli實(shí)現(xiàn)將數(shù)據(jù)庫(kù)中一張表信息打印到表格里的方法
這篇文章主要介紹了php+mysqli實(shí)現(xiàn)將數(shù)據(jù)庫(kù)中一張表信息打印到表格里的方法,涉及mysqli查詢的相關(guān)技巧,需要的朋友可以參考下2015-01-01使用PHP遍歷文件目錄與清除目錄中文件的實(shí)現(xiàn)詳解
本篇文章是對(duì)使用PHP遍歷文件目錄與清除目錄中文件的實(shí)現(xiàn)方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06