本文翻譯自
Symfony
作者 Fabien Potencier 的 《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》 系列文章。
- Part 1: What is Dependency Injection?
- Part 2: Do you need a Dependency Injection Container?
- Part 3: Introduction to the Symfony Service Container
- Part 4: Symfony Service Container: Using a Builder to create Services
- Part 5: Symfony Service Container: Using XML or YAML to describe Services
- Part 6: The Need for Speed
專有名詞翻譯成中文後會變得不利於理解,後續文章中將改用括號+中文備註的形式。
上文我透過一些示例講解了 Dependency Injection
,本文將接著介紹 Dependency Injection Containers (容器)
的概念。
首先記住這句話:
大多數時候,
Dependency Injection
並不需要Container
。
只有當你需要管理一大堆具有很多依賴關係的不同物件時,Container
才會非常有用(例如框架中)。
上文書,建立 User
物件需要先建立 SessionStorate
物件。這裡的有個瑕疵,建立物件時需要提前知道它所有的依賴項:
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
以 Zend Framework
中 Zend_Mail
庫傳送郵件過程為例:
$transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', [
'auth' => 'login',
'username' => 'foo',
'password' => 'bar',
'ssl' => 'ssl',
'port' => 465,
]);
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($transport);
請把這個例子看做一個大系統中的一小部分,因為這種簡單的例子當然沒必要用
Container
。
Dependency Injection Container
是一個“知道如何例項化和配置物件”的物件(工廠模式的昇華)。為了做到這點,它需要知道建構函式的引數、以及物件之間的關係。
下面是一個寫死 Zend_Mail
的 Container
:
class Container
{
public function getMailTransport()
{
return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [
'auth' => 'login',
'username' => 'foo',
'password' => 'bar',
'ssl' => 'ssl',
'port' => 465,
]);
}
public function getMailer()
{
$mailer = new Zend_Mail();
$mailer->setDefaultTransport($this->getMailTransport());
return $mailer;
}
}
這個 Container
用起來就相當簡單了:
$container = new Container();
$mailer = $container->getMailer();
我們只管向 Container
要 mailer
物件就行,完全不用管 mailer
怎麼建立。建立 mailer
物件的“雜活”是嵌入在 Container
中的。Container
透過 getMailTransport()
方法,把 Zend_Mail_Transport_Smtp
這個依賴自動注入到了 Zend_Mail
中。
細心的網友可能已經發現,這裡的 Container
把什麼都寫死了。我們可以完善一下:
class Container
{
protected $parameters = array();
public function __construct(array $parameters = [])
{
$this->parameters = $parameters;
}
public function getMailTransport()
{
return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [
'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;
}
}
現在就可以隨時更改 username
和 password
了:
$container = new Container([
'mailer.username' => 'foo',
'mailer.password' => 'bar',
]);
$mailer = $container->getMailer();
如果需要更改 mailer
類,把類名也當引數傳入就行:
class Container
{
// ...
public function getMailer()
{
$class = $this->parameters['mailer.class'];
$mailer = new $class();
$mailer->setDefaultTransport($this->getMailTransport());
return $mailer;
}
}
$container = new Container([
'mailer.username' => 'foo',
'mailer.password' => 'bar',
'mailer.class' => 'Zend_Mail',
]);
$mailer = $container->getMailer();
如果想每次獲取同一個 mailer
例項,可以用 單例模式
:
class Container
{
static protected $shared = [];
// ...
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;
}
}
這就包含了 Dependency Injection Containers
的基本功能:
Container
管理物件例項化到配置的過程- 物件本身不知道自己是由
Container
管理的,對Container
一無所知。
這就是為什麼 Container
能夠管理任何 PHP 物件。 物件使用 DI
來管理依賴關係非常好,但不是必須的。
Container
很容易實現,但手工維護各種亂七八糟的物件還是很麻煩。下一章我將介紹 Laravel
中 Container
的實現方式。
作者下一章原文中講的是
Container
在Symfony 2
中的實現,我會把它換成Laravel
。
本作品採用《CC 協議》,轉載必須註明作者和本文連結