PHP实现依赖项注入模式容器示例
依赖项注入模式在许多应用程序中得到了广泛的应用。
什么是依赖项注入模式
依赖项注入模式实际上是一个非常简单的模式。
让我们看一下下面的代码
class Mailer { private $transport; private $emailTemplate; public function __construct(){ $this->transport = new Smtp('host', 'port', 'user', 'pass'); $this->emailTemplate = file_get_contents('template.html'); } public function send($from, $to, $subject){ $this->transport->send($from, $to, $subject, $this->emailTemplate); } } $mailer = new Mailer(); $mailer->send('theitroad', '[email protected]', '测试邮件');
我们定义了一个简单的Mailer类,这个类加载HTML模板后将其发送到指定的邮箱。
我们使用了smtp来发送邮件。但是如果这是要求使用第三方API(比如qq邮箱)发送邮件。那么这个类不符合要求。
我们需要把smtp代码替换成QQ邮箱的api。这种紧密耦合的方式不灵活。
解决的方法是,不要对Smtp类进行硬编码,而是将其作为配置数据提供给Mailer类。
先创建Smtp类的实例,然后通过Mailers的构造函数或者setter方法将其注入Mailer类中,可以完成此操作。
同时,如果没有 $transport,Mailer类就无法运行。
所以就叫做 依赖项注入模式
class Mailer { private $transport; private $emailTemplate; public function __construct($transport){ $this->transport = $transport; $this->emailTemplate = file_get_contents('template.html'); } public function send($from, $to, $subject){ $this->transport->send($from, $to, $subject, $this->emailTemplate); } } class Smtp { public function send($from, $to, $subject, $message){ // Send the message using smtp. } } $mailer = new Mailer(new Smtp()); $mailer->send('Syed Hussain', '[email protected]', 'Marketing Email');
所以依赖注入是向依赖于其他服务(Smpt类)的客户端(Mailer类)提供配置数据的能力。
您可能已经在某个时候有意或无意地使用了依赖项注入模式。依赖项注入也称为控制反转(IoC)。这个名称来自于这样一个事实:来自客户端的控制被倒置了,这意味着客户端不再对它的依赖项负责,而控制被交给了另一个实体。
如果从客户端删除依赖项,则意味着必须在另一个范围中创建依赖项。这里使用的术语范围是“其他地方”,这些依赖项可能有它们自己的依赖项。在任何情况下,所有依赖项必须存在,依赖项(客户端)才能使用它们。这样做的副作用是代码膨胀,并引入错误。例如,您可能知道某个类需要哪些依赖项,但是您的同事可能不知道,而且可能使用不同的依赖项,这可能会导致错误。这就是主要使用依赖注入容器(DIC)的地方。DIC是一种实现,可以用任何编程语言实现。
依赖注入容器(DIC)
依赖注入容器(DIC)可以看作是注册表。每个依赖项都有一个名称,可以是类名或标识该依赖项的唯一名称。
另外,可以为每个依赖项配置依赖项所需的参数。上面依赖项Smtp的参数有主机,端口,用户名和密码。
将所有依赖项注册到一个容器中之后,该容器负责使用get()方法返回这些依赖项的实例。
一般依赖注入容器存储的是依赖项类名称的引用。只有当需要依赖项时,才会去动态进行实例化。如果存储的是依赖项类的实例。会导致性能问题。
示例-简单的依赖注入容器
让我们看一下简单的依赖注入容器的示例代码:
创建DiContainer的实例并注册一个依赖项。
$DiContainer->register('db', 'PDO') ->addArgument('mysql:host=localhost;dbname=db') ->addArgument('username') ->addArgument('password');
register方法注册一个依赖项,并接受两个参数。第一个参数是已赋予依赖项的唯一名称。也就是查找依赖项时使用的名称。第二个参数是依赖项的类名。
addArgument()方法用于添加PDO类构造函数所需的参数。
在注册了依赖项之后,现在可以通过三种不同的方式对其进行访问
$db = $DiContainer->getInstance('db'); $db = $DiContainer->getSingleInstance('db'); $db = $DiContainer->db;
- 使用依赖项名称调用getInstance()方法,将返回依赖项的新实例。
- 调用getSingleInstance()方法将仅返回一次实例。DiContainer将在内部维护此实例。
- 最后一种方法允许您将依赖项名称用作属性。
在某些情况下,可能希望使用已注册的依赖项作为正在注册的另一个依赖项的参数。下面的代码示例显示了如何解决此问题。
class Mailer { private $transport; public function __construct($transport){ $this->transport = $transport; } } class Smtp{ public function __construct($host, $port, $user, $pass){ // Process arguments } } $DiContainer = new DiContainer(); $DiContainer->register('smtp', 'Smtp') ->addArgument('host') ->addArgument('port') ->addArgument('user') ->addArgument('pass'); $DiContainer->register('mailer', 'Mailer') ->addArgument('@@smtp'); $mailer = $DiContainer->getInstance('mailer');
依赖项smtp已被注册并命名为smtp。Mailer类需要将smtp实例传递给其构造函数。注册Mailer依赖项时,双@@后跟依赖项名称用于引用先前注册的依赖项。
DIC的实现方式不同,每种编程语言都有自己的语法。一些DIC不仅将参数传递给类构造函数,甚至可以使用参数调用类方法。
PHP的简单反射功能使任何人都可以轻松开发自己的DIC库。
DiContainer类
<?php class DiContainer { protected $services = array(); protected $instances = array(); public function register($serviceName, $className){ $service = new Service($className, $this); $this->services[$serviceName] = $service; return $service; } public function getInstance($serviceName){ if(!isset($this->services[$serviceName])){ throw new RuntimeException(sprintf("Failed to get service '%s'. Service is not registered", $serviceName)); } $service = $this->services[$serviceName]; $refClass = new ReflectionClass($service->getClassName()); return $refClass->newInstanceArgs($service->getArguments()); } public function getSingleInstance($serviceName){ if(isset($this->instances[$serviceName])){ return $this->instances[$serviceName]; } $instance = $this->getInstance($serviceName); $this->instances[$serviceName] = $instance; return $instance; } public function __get($serviceName){ return $this->getInstance($serviceName); } } class Service{ protected $className; protected $diContainer; protected $serviceArgs = array(); public function __construct($className, $diContainer){ $this->className = $className; $this->diContainer = $diContainer; } public function getClassName(){ return $this->className; } public function addArgument($argument){ if(is_string($argument)){ if (substr($argument,0,2)=='@@'){ $serviceName = substr($argument,2); $argument = $this->diContainer->getInstance($serviceName); } } $this->serviceArgs[] = $argument; return $this; } public function getArguments(){ return $this->serviceArgs; } } ?>