译文首发于 是否须要使用依赖注入容器?,转载请注明出处。
本文是依赖注入(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 是如何实现「依赖注入容器」的。