breezegao 2019-06-27
本文是依赖注入(Depeendency Injection)系列教程的第 3 篇文章,本系列教程主要讲解如何使用 PHP 实现一个轻量级服务容器,教程包括:
从本系列的开篇到现在我们基本还是围绕「依赖注入」的基本概念展开的。前两篇入门的文章对于理解本文及后续教程至关重要。现在,是时候该去探索 Symfony 2 服务容器是如何实现这个主题了。
Symfony 中的「依赖注入容器」定义的类名为「sfServiceContainer」。这是一个非常轻量级的类,实现了 [上一篇]() 文章中讲解到的基本功能。
Symfony 服务容器可以到官方 Svn 版本库中获得: http://svn.symfony-project.co...。注意, Symfony 组件依旧保持更新,这也意味着它的实现可能与本文有所出入。(译注: @todo)在 Symfony 中,任何服务的实例都有容器管理。前一篇文章中提到的 Zend_Mail 实例中,就需要使用到两个服务:mailer 服务和 mail_transport 服务。
<?php class Container { static protected $sharged = array(); protected $parameters = array(); public function __construct(array $parameters = array()) { $this->parameters = $parameters; } public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smpt.gmail.com', array( 'auth' => 'login', 'username' => $this->parameters['mailer.username'], 'password' => $this->parameters['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } 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; } }
让容器类 Container 继承 sfServiceContainer 类的话,可以有效精简代码:
<?php class Container extends sfServiceContainer { static protected $shared = array(); protected function getMailTransportService() { return new Zend_Mail_Transport_Smtp('smpt.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; } }
似乎与之前相差无几,但通过继承 spServiceProvider 的容器类拥有更多功能并且代码更整洁。这里列几点主要的异同点:
一个服务标识符必须唯一,并且仅可以包含字母、数字、下划线和 .(英文点号)。. 号在容器内的功能类似于「命名空间」(如 mail.mailer 和 mail.transport 实例)。
接下来是如何使用新的容器类:
<?php require_once 'PATH/TO/sf/lib/sfServiceContainerAutoloader.php'; sfServiceContainerAutoloader::register(); $sc = new Container(array( 'mailer.username' => 'foo', 'mailer.password' => 'bar', 'mailer.class' => 'Zend_Mail', )); $mailer = $sc->mailer;
现在,由于我们继承 spServiceContainer 容器类,我们可以使用更为整洁的接口功能:
<?php if ($sc->hasService('mailer')) { $mailer = $sc->getService('mailer'); } $sc->setService('mailer', $mailer);
<?php if (isset($sc->mailer)) { $mailer = $sc->mailer; } $sc->mailer = $mailer;
<?php if (!$sc->hasParameter('mailer_class')) { $sc->setParameter('mailer_class', 'Zend_Mail'); } echo $sc->getParameter('mailer_class'); // 重写容器所有参数 $sc->setParameters($parameters); // 向容器添加参数 $sc->addParameters($parameters);
<?ph if (!isset($sc['mailer.class'])) { $sc['mailer.class'] = 'Zend_Mail'; } $mailerClass = $sc['mailer.class'];
<?php foreach ($sc as $id => $service) { echo sprintf("Service %s is an instance of %s.\n", $id, get_class($service)); }
当项目容器需要管理不太多的服务时,通过继承 spServiceContainer 类是非常明智的选择;即使,这样依旧需要处理大量的基础工作或直接从已有项目中复制代码过来。而当系统引入大量的服务时,我们就需要使用更好的方法来组织和管理这些服务。
这就是为什么多数时候我们并不会直接使用 spServiceContainer 类的原因。但是我们花这个时间来讲解 spServiceContainer 类的用法的理由是,它是 Symfony 依赖注入容器实现的基石。
下一篇文章,我们将来看看可以简化服务定义过程的 sfServiceContainerBuilder 类。