在前面五篇文章中,我们逐步介绍了依赖注入这种简单且有效的设计模式背后的重点概念。我们也讲了Symfony 2中实现这种轻量级容器的方法。
但随着介绍XML和YAML配置文件,你可能会有点怀疑容器本身的性能。即使服务延迟加载,在每次请求和创建对象时,通过读取一堆XML或YAML文件以内省的方式创建对象,并不是非常有效的。由于容器是使用它的应用程序的基石,它的速度确实很重要。
一方面,使用XML或YAML描述Web服务及其配置是非常强大和灵活的:
<container xmlns="http://symfony-project.org/2.0/container">
<parameters>
<parameter key="mailer.username">fooparameter>
<parameter key="mailer.password">barparameter>
<parameter key="mailer.class">Zend_Mailparameter>
parameters>
<services>
<service id="mail.transport" class="Zend_Mail_Transport_Smtp" shared="false">
<argument>smtp.gmail.comargument>
<argument type="collection">
<argument key="auth">loginargument>
<argument key="username">%mailer.username%argument>
<argument key="password">%mailer.password%argument>
<argument key="ssl">sslargument>
<argument key="port">trueargument>
argument>
service>
<service id="mailer" class="%mailer.class%">
<call method="setDefaultTransport">
<argument type="service" id="mail.transport" />
call>
service>
services>
container>
但另一方面,用普通的PHP类来定义服务容器可以保证充分的速度,在这个系列的第二篇文章中看到:
class Container extends sfServiceContainer
{
static protected $shared = array();
protected function getMailTransportService()
{
return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
'auth' => 'login',
'username' => $this['mailer.username'],
'password' => $this['mailer.password'],
'ssl' => 'ssl',
'port' => 465,
));
}
protected function getMailerService()
{
if (isset(self::$shared['mailer']))
{
return self::$shared['mailer'];
}
$class = $this['mailer.class'];
$mailer = new $class();
$mailer->setDefaultTransport($this->getMailTransportService());
return self::$shared['mailer'] = $mailer;
}
}
上面的代码通过配置变量提供了有限的灵活性,由于是PHP代码所以仍然效率很高。
怎么能同时保证系统即灵活又高效?很简单,symfony的依赖注入组件提供了另一个内置的PHP类:PHP dumper。 dumper可以将任何服务容器转换成普通的PHP代码。没错,它能够生成最开始你手写的那些代码。
再次通过Zend_Mail的例子来演示PHP dumper类。为了简要,让我们使用在前面的文章中创建的XML定义文件
$sc = new sfServiceContainerBuilder();
$loader = new sfServiceContainerLoaderFileXml($sc);
$loader->load('/somewhere/container.xml');
$dumper = new sfServiceContainerDumperPhp($sc);
$code = $dumper->dump(array('class' => 'Container'));
file_put_contents('/somewhere/container.php', $code);
和其它的 dumper一样, sfServiceContainerDumperPhp类也是接收一个容器对像作为构造函数的第一个参数。dump()方法接收一个数组,其中有一个是要生成的类的名称。
以下是生成的代码:
class Container extends sfServiceContainer
{
protected $shared = array();
public function __construct()
{
parent::__construct($this->getDefaultParameters());
}
protected function getMailTransportService()
{
$instance = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array(
'auth' => 'login',
'username' => $this->getParameter('mailer.username'),
'password' => $this->getParameter('mailer.password'),
'ssl' => 'ssl',
'port' => 465
));
return $instance;
}
protected function getMailerService()
{
if (isset($this->shared['mailer'])) return $this->shared['mailer'];
$class = $this->getParameter('mailer.class');
$instance = new $class();
$instance->setDefaultTransport($this->getMailTransportService());
return $this->shared['mailer'] = $instance;
}
protected function getDefaultParameters()
{
return array (
'mailer.username' => 'foo',
'mailer.password' => 'bar',
'mailer.class' => 'Zend_Mail',
);
}
}
请仔细观察这些dumper生成的代码,你会发现和我们手写的很像。
利用sfServiceContainerDumperPhp,你可以同时拥有两个好处:XML或YAML格式来描述和配置服务保证了灵活性,通过自动生成PHP文件保证了速度。
当然,项目总是为不同的环境作不同的设置,所以你可以生成不同的容器类,基于环境或调试设置。这里是一小段PHP代码举了一个例子:当不处于调式模式,在第一次请求时,如何动态创建容器,然后在所有其他请求时使用缓存。
$name = 'Project'.md5($appDir.$isDebug.$environment).'ServiceContainer';
$file = sys_get_temp_dir().'/'.$name.'.php';
if (!$isDebug && file_exists($file))
{
require_once $file;
$sc = new $name();
}
else
{
// build the service container dynamically
$sc = new sfServiceContainerBuilder();
$loader = new sfServiceContainerLoaderFileXml($sc);
$loader->load('/somewhere/container.xml');
if (!$isDebug)
{
$dumper = new sfServiceContainerDumperPhp($sc);
file_put_contents($file, $dumper->dump(array('class' => $name));
}
}
以上包括了所有的Symfony 2依赖注入容器内容。
在结束这个系列之前,我仍然要展示dumpers的另一个牛逼的功能。Dumpers能做很多不同的功能。为了演示组件执行中的解耦,我实现了Graphviz dumper。为毛?主要是帮助我们可视化的查看这些服务之间的依赖关系。
首先,看看在我们例子如何使用
$dumper = new sfServiceContainerDumperGraphviz($sc);
file_put_contents('/somewhere/container.dot', $dumper->dump());
Graphviz dumper会生成一个容器的dot描述
digraph sc {
ratio="compress"
node [fontsize="11" fontname="Myriad" shape="record"];
edge [fontsize="9" fontname="Myriad" color="grey" arrowhead="open" arrowsize="0.5"];
node_service_container [label="service_container\nsfServiceContainerBuilder\n", shape=record, fillcolor="#9999ff", style="filled"];
node_mail_transport [label="mail.transport\nZend_Mail_Transport_Smtp\n", shape=record, fillcolor="#eeeeee", style="dotted"];
node_mailer [label="mailer\nZend_Mail\n", shape=record, fillcolor="#eeeeee", style="filled"];
node_mailer -> node_mail_transport [label="setDefaultTransport()" style="dashed"];
}
这些描述可以通过dot程序转换成一张image图
$ dot -Tpng /somewhere/container.dot > /somewhere/container.png
Graphviz dumper的dumper方法可以接收不同的参数,调整图像输出。
Graphviz dumper未来会作为symfony的组件集成进来,这是是一个假设的CMS使用symfony 2新的模板框架:
这就是这个系列所有的内容了。我希望你通过这些文章学到了一些东西。我也希望你能尝试使用symfony 2服务容器组件,并且尽快给我你的使用反馈。