譯文首發於 是否須要使用依賴注入容器?,轉載請註明出處。
本文是依賴注入(Depeendency Injection)系列教程的第 2 篇文章,本系列教程主要講解如何使用 PHP 實現一個輕量級服務容器,教程包括:php
在上一篇 什麼是依賴注入 一文中,我從 Web 項目的角度出發,結合實例講解了「依賴注入」的具體實現。這一篇文章將談談「依賴注入容器」。html
首先,表名個人觀點:性能優化
通常使用「依賴注入」就夠了,極少數狀況須要使用「依賴注入容器」。併發
僅當須要管理大量依賴組件的實例時,才能真正體現「依賴注入容器」的價值(好比一個框架)。框架
若是你還記得 什麼是依賴注入 中講到的例子,在建立 User 實例以前,須要先建立 SessionStorage 實例。其實,這樣也沒什麼很差,只不過您須要在充分了解全部依賴的組件後,才能着手建立對應的實例。函數
<?php $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
本篇文章接下來的內容,咱們將討論 PHP 實現相似 Symfony 2 的「依賴注入容器」。我想明確的是,在實現「依賴注入容器」時不涉及 Symfony 相關功能,因此我將使用 Zend 框架示例來講明。性能
這邊不涉及框架之爭。我很是感謝 Zend 框架組件,事實上,Symfony 框架使用了許多 Zend 框架中的組件。
Zend Framework 的郵件組件能夠輕鬆處理郵件管理工做,一般咱們會使用 PHP 內建的 Mail() 函數發送電子郵件,但這不利於擴展。值得慶幸的是,使用 Zend 的郵件組件經過設置發送對象來修改郵件發送行爲很是容易。如何使用 Gmail 賬號做爲發送者建立 Zend_Mail 實例併發送一封郵件:測試
<?php $transport = new Zend_Mail_Transport_Stmp('stmp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); $mailer = new Zend_Mail(); $mailer->setDefaultTransport($transport);
爲了使這篇文章簡潔,我會使用一些簡單的示例。固然,實際項目中對於如此簡單的功能,其實沒有必要去使用「容器」。那麼把這個例子看成由容器管理的衆多實例集合中的一個部分就能夠了。優化
「依賴注入容器」是一個知道如何去實例化和配置依賴組件的對象。爲了完成這樣的工做,「依賴注入容器」須要知道構造函數參數及其對應的依賴組件的對應關係。ui
下面以硬編碼的方式實現一個 Zend_Mail 容器:
<?php class Container { public function getMailTransport() { return new Zend_Mail_Transport_Stmp('stmp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
使用這個容器類也很簡單:
<?php $container = new Container(); $mailer = $container->getMailer();
在使用容器時,咱們只須要獲取一個 mailer 對象,而無需知道它是如何建立的;有關 mailer 實例建立的全部細節都有這個容器完成。mailer 對象所依賴的傳輸對象由調用容器的 getMailTransport() 方法自動注入到 mailer 對象中。容器的魔力僅需一個簡單的方法調用便可實現。
等等,聰明如你怎麼可能沒有看出這個容器還不夠完美呢 -- 它包含硬編碼!所以,咱們須要更進一步,將所須要的數據以構造函數的參數形式添加到容器內會更好:
<?php class Container { protected $parameters = array(); public function __construct(array $parameters = array()) { $this->parameters = $parameters; } public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => $this->parameters['mailer.username'], 'password' => $this->parameters['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
如今能夠很容易的修改 Gmail 賬號的用戶名和密碼了:
<?php $container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', )); $mailer = $container->getMailer();
若是須要修改這個郵件發送器實現用於測試,還能夠將郵件發送器類名做爲參數設置到容器:
<?php class Container { public function getMailer() { $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } } $container = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', 'mailer.class' => 'Zend_Mail', )); $mailer = $container->getMailer();
最後,一些優化,每次我想要獲取一個郵件發送器實例 $mailer ,都須要建立一個新的實例。所以,能夠將容器更改成始終返回相同的對象:
<?php class Container { static protected $shared = array(); public function getMailer() { if (isset(self::$shared['mailer'])) { return self::$shared['mailer']; } $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return self::$shared['mailer'] = $mailer; } }
因爲引入了一個 $shared 靜態成員變量,這樣每次調用 getMailer() 方法時,都會返回首次調用時建立的對象實例。
上面咱們總結了依賴注入容器須要實現的基本特性。「依賴注入容器」用於管理依賴的對象實例:包含依賴組件的實例化和對組件所需配置的管理。依賴組件並不知道它是由容器管理的,或許依賴組件根本就不知道「依賴注入容器」的存在。這就是爲何容器可以管理任何 PHP 對象的奧祕。甚至,若是這些實例也使用依賴注入來管理自身的依賴,那就更加完美了,但這不是先決條件。
固然,人肉建立和維護容器類會很快成爲一場噩夢。可是因爲容器的需求很是小,因此很容易實現。接下類的文章,將討論 Symfony 2 是如何實現「依賴注入容器」的。