依賴自動(dòng)注入
在webman里依賴自動(dòng)注入是可選功能,此功能默認(rèn)關(guān)閉。如果你需要依賴自動(dòng)注入,推薦使用php-di,以下是webman結(jié)合php-di
的用法。
安裝
composer require psr/container ^1.1.1 php-di/php-di ^6.3 doctrine/annotations ^1.14
修改配置config/container.php
,其最終內(nèi)容如下:
$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(config('dependence', []));
$builder->useAutowiring(true);
$builder->useAnnotations(true);
return $builder->build();
config/container.php
里最終返回一個(gè)符合PSR-11
規(guī)范的容器實(shí)例。如果你不想使用php-di
,可以在這里創(chuàng)建并返回一個(gè)其它符合PSR-11
規(guī)范的容器實(shí)例。
構(gòu)造函數(shù)注入
新建app/service/Mailer.php
(如目錄不存在請自行創(chuàng)建)內(nèi)容如下:
<?php
namespace app\service;
class Mailer
{
public function mail($email, $content)
{
// 發(fā)送郵件代碼省略
}
}
app/controller/UserController.php
內(nèi)容如下:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
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');
}
}
正常情況下,需要以下代碼才能完成app\controller\UserController
的實(shí)例化:
$mailer = new Mailer;
$user = new UserController($mailer);
當(dāng)使用php-di
后,開發(fā)者無需手動(dòng)實(shí)例化控制器中的Mailer
,webman會(huì)自動(dòng)幫你完成。如果在實(shí)例化Mailer
過程中有其它類的依賴,webman也會(huì)自動(dòng)實(shí)例化并注入。開發(fā)者不需要任何的初始化工作。
注意
必須是由框架或者php-di
創(chuàng)建的實(shí)例才能完成依賴自動(dòng)注入,手動(dòng)new
的實(shí)例無法完成依賴自動(dòng)注入,如需注入,需要使用support\Container
接口替換new
語句,例如:
use app\service\UserService;
use app\service\LogService;
use support\Container;
// new關(guān)鍵字創(chuàng)建的實(shí)例無法依賴注入
$user_service = new UserService;
// new關(guān)鍵字創(chuàng)建的實(shí)例無法依賴注入
$log_service = new LogService($path, $name);
// Container創(chuàng)建的實(shí)例可以依賴注入
$user_service = Container::get(UserService::class);
// Container創(chuàng)建的實(shí)例可以依賴注入
$log_service = Container::make(LogService::class, [$path, $name]);
注解注入
除了構(gòu)造函數(shù)依賴自動(dòng)注入,我們還可以使用注解注入。繼續(xù)上面的例子,app\controller\UserController
更改成如下:
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
這個(gè)例子通過 @Inject
注解注入,并且由 @var
注解聲明對象類型。這個(gè)例子和構(gòu)造函數(shù)注入效果一樣,但是代碼更精簡。
注意
webman在1.4.6版本之前不支持控制器參數(shù)注入,例如以下代碼當(dāng)webman<=1.4.6時(shí)是不支持的
<?php
namespace app\controller;
use support\Request;
use app\service\Mailer;
class UserController
{
// 1.4.6版本之前不支持控制器參數(shù)注入
public function register(Request $request, Mailer $mailer)
{
$mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
自定義構(gòu)造函數(shù)注入
有時(shí)候構(gòu)造函數(shù)傳入的參數(shù)可能不是類的實(shí)例,而是字符串、數(shù)字、數(shù)組等數(shù)據(jù)。例如Mailer構(gòu)造函數(shù)需要傳遞smtp服務(wù)器ip和端口:
<?php
namespace app\service;
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)
{
// 發(fā)送郵件代碼省略
}
}
這種情況無法直接使用前面介紹的構(gòu)造函數(shù)自動(dòng)注入,因?yàn)?code>php-di無法確定$smtp_host
$smtp_port
的值是什么。這時(shí)候可以嘗試自定義注入。
在config/dependence.php
(文件不存在請自行創(chuàng)建)中加入如下代碼:
return [
// ... 這里忽略了其它配置
app\service\Mailer::class => new app\service\Mailer('192.168.1.11', 25);
];
這樣當(dāng)依賴注入需要獲取app\service\Mailer
實(shí)例時(shí)將自動(dòng)使用這個(gè)配置中創(chuàng)建的app\service\Mailer
實(shí)例。
我們注意到,config/dependence.php
中使用了new
來實(shí)例化Mailer
類,這個(gè)在本示例沒有任何問題,但是想象下如果Mailer
類依賴了其它類的話或者Mailer
類內(nèi)部使用了注解注入,使用new
初始化將不會(huì)依賴自動(dòng)注入。解決辦法是利用自定義接口注入,通過Container::get(類名)
或者 Container::make(類名, [構(gòu)造函數(shù)參數(shù)])
方法來初始化類。
自定義接口注入
在現(xiàn)實(shí)項(xiàng)目中,我們更希望面向接口編程,而不是具體的類。比如app\controller\UserController
里應(yīng)該引入app\service\MailerInterface
而不是app\service\Mailer
。
定義MailerInterface
接口。
<?php
namespace app\service;
interface MailerInterface
{
public function mail($email, $content);
}
定義MailerInterface
接口的實(shí)現(xiàn)。
<?php
namespace app\service;
class Mailer implements MailerInterface
{
private $smtpHost;
private $smtpPort;
public function __construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
public function mail($email, $content)
{
// 發(fā)送郵件代碼省略
}
}
引入MailerInterface
接口而非具體實(shí)現(xiàn)。
<?php
namespace app\controller;
use support\Request;
use app\service\MailerInterface;
use DI\Annotation\Inject;
class UserController
{
/**
* @Inject
* @var MailerInterface
*/
private $mailer;
public function register(Request $request)
{
$this->mailer->mail('hello@webman.com', 'Hello and welcome!');
return response('ok');
}
}
config/dependence.php
將 MailerInterface
接口定義如下實(shí)現(xiàn)。
use Psr\Container\ContainerInterface;
return [
app\service\MailerInterface::class => function(ContainerInterface $container) {
return $container->make(app\service\Mailer::class, ['smtp_host' => '192.168.1.11', 'smtp_port' => 25]);
}
];
這樣當(dāng)業(yè)務(wù)需要使用MailerInterface
接口時(shí),將自動(dòng)使用Mailer
實(shí)現(xiàn)。
面向接口編程的好處是,當(dāng)我們需要更換某個(gè)組件時(shí),不需要更改業(yè)務(wù)代碼,只需要更改
config/dependence.php
中的具體實(shí)現(xiàn)即可。這在做單元測試也非常有用。
其它自定義注入
config/dependence.php
除了能定義類的依賴,也能定義其它值,例如字符串、數(shù)字、數(shù)組等。
例如config/dependence.php
定義如下:
return [
'smtp_host' => '192.168.1.11',
'smtp_port' => 25
];
這時(shí)候我們可以通過@Inject
將smtp_host
smtp_port
注入到類的屬性中。
<?php
namespace app\service;
use DI\Annotation\Inject;
class Mailer
{
/**
* @Inject("smtp_host")
*/
private $smtpHost;
/**
* @Inject("smtp_port")
*/
private $smtpPort;
public function mail($email, $content)
{
// 發(fā)送郵件代碼省略
echo "{$this->smtpHost}:{$this->smtpPort}\n"; // 將輸出 192.168.1.11:25
}
}
注意:
@Inject("key")
里面是雙引號
更多內(nèi)容
請參考php-di手冊