依赖注入容器
依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。
安装
代码语言:javascript复制composer require php-di/php-di
基本用法
1.使用依赖注入
首先,让我们使用依赖注入来编写代码,而不考虑PHP-DI:
代码语言:javascript复制class Mailer
{
public function mail($recipient, $content)
{
// send an email to the recipient
}
}
代码语言:javascript复制class UserManager
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register($email, $password)
{
// The user just registered, we create his account
// ...
// We send him an email to say hello!
$this->mailer->mail($email, 'Hello and welcome!');
}
}
正如我们所看到的, UserManager
将 Mailer
作为构造器参数:这就是依赖注入!
2.创建容器
您可以非常轻松地创建一个为开发预先配置的容器实例:
代码语言:javascript复制$container = new DIContainer();
如果你想注册定义文件(在PHP定义中解释)或调整一些选项,你可以使用容器构建器:
代码语言:javascript复制$builder = new DIContainerBuilder();
$builder->...
$container = $builder->build();
3.创建对象
如果没有PHP-DI,我们将不得不像这样手动“连接”依赖项:
代码语言:javascript复制$mailer = new Mailer();
$userManager = new UserManager($mailer);
相反,我们可以让PHP-DI找出依赖关系:
代码语言:javascript复制$userManager = $container->get('UserManager');
在后台,PHP-DI将创建一个Mailer对象和一个UserManager对象。
它怎么知道要注入什么?容器使用一种称为自动装配的技术。这不是PHP-DI独有的,但这仍然很棒。它将扫描代码并查看构造函数中需要哪些参数。
在我们的示例中, UserManager
构造函数接受一个 Mailer
对象:PHP-DI知道它需要创建一个对象。非常简单,但非常有效。
webman 框架应用
在webman里依赖自动注入是可选功能,此功能默认关闭。如果你需要依赖自动注入,推荐使用php-di,以下是webman结合php-di的用法。
安装
代码语言:javascript复制composer require psr/container ^1.1.1 php-di/php-di ^6 doctrine/annotations ^1.14
修改配置config/container.php
,其最终内容如下:
$builder = new DIContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
config/container.php
里最终返回一个符合PSR-11
规范的容器实例。如果你不想使用php-di
,可以在这里创建并返回一个其它符合PSR-11
规范的容器实例。
构造函数注入
新建app/service/Mailer.php
(如目录不存在请自行创建)内容如下:
<?php
namespace appservice;
class Mailer
{
public function mail($email, $content)
{
// 发送邮件代码省略
}
}
app/controller/UserController.php
内容如下:
<?php
namespace appcontroller;
use supportRequest;
use appserviceMailer;
class UserController
{
private $mailer;
public function __construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
正常情况下,需要以下代码才能完成appcontrollerUserController
的实例化:
$mailer = new Mailer;
$user = new UserController($mailer);
当使用php-di
后,开发者无需手动实例化控制器中的Mailer
,webman会自动帮你完成。如果在实例化Mailer
过程中有其它类的依赖,webman也会自动实例化并注入。开发者不需要任何的初始化工作。
代码语言:javascript复制注意必须是由框架或者
php-di
创建的实例才能完成依赖自动注入,手动new
的实例无法完成依赖自动注入,如需注入,需要使用supportContainer
接口替换new
语句,例如:
use appserviceUserService;
use appserviceLogService;
use supportContainer;
// new关键字创建的实例无法依赖注入
$user_service = new UserService;
// new关键字创建的实例无法依赖注入
$log_service = new LogService($path, $name);
// Container创建的实例可以依赖注入
$user_service = Container::get(UserService::class);
// Container创建的实例可以依赖注入
$log_service = Container::make(LogService::class, [$path, $name]);
注解注入
除了构造函数依赖自动注入,我们还可以使用注解注入。继续上面的例子,appcontrollerUserController
更改成如下:
<?php
namespace appcontroller;
use supportRequest;
use appserviceMailer;
use DIAnnotationInject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
这个例子通过 @Inject
注解注入,并且由 @var
注解声明对象类型。这个例子和构造函数注入效果一样,但是代码更精简。
代码语言:javascript复制注意webman在1.4.6版本之前不支持控制器参数注入,例如以下代码当webman<=1.4.6时是不支持的
<?php
namespace appcontroller;
use supportRequest;
use appserviceMailer;
class UserController
{
// 1.4.6版本之前不支持控制器参数注入
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
自定义构造函数注入
有时候构造函数传入的参数可能不是类的实例,而是字符串、数字、数组等数据。例如Mailer构造函数需要传递smtp服务器ip和端口:
代码语言:javascript复制<?php
namespace appservice;
class Mailer
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// 发送邮件代码省略
}
}
这种情况无法直接使用前面介绍的构造函数自动注入,因为php-di无法确定smtp_host smtp_port的值是什么。这时候可以尝试自定义注入。
在config/dependence.php
(文件不存在请自行创建)中加入如下代码:
return [
// ... 这里忽略了其它配置
appserviceMailer::class => new appserviceMailer('192.168.1.11', 25);
];
这样当依赖注入需要获取appserviceMailer
实例时将自动使用这个配置中创建的appserviceMailer
实例。
我们注意到,config/dependence.php
中使用了new
来实例化Mailer
类,这个在本示例没有任何问题,但是想象下如果Mailer
类依赖了其它类的话或者Mailer
类内部使用了注解注入,使用new
初始化将不会依赖自动注入。解决办法是利用自定义接口注入,通过Container::get(类名)
或者 Container::make(类名, [构造函数参数])
方法来初始化类。