依賴註入容器
依賴註入(Dependency Injection,DI)容器就是一個物件,它知道怎樣初始化並配置物件及其依賴的所有物件。
安裝
composer require php-di/php-di
基本用法
1.使用依賴註入
首先,讓我們使用依賴註入來編寫程式碼,而不考慮PHP-DI:
class Mailer
{
public function mail($recipient, $content)
{
// send an email to the recipient
}
}
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.建立容器
您可以非常輕松地建立一個為開發預先配置的容器例項:
$container = new DI\Container();
如果你想註冊定義檔(在PHP定義中解釋)或調整一些選項,你可以使用容器構建器:
$builder = new DI\ContainerBuilder();
$builder->...
$container = $builder->build();
3.建立物件
如果沒有PHP-DI,我們將不得不像這樣手動「連線」依賴項:
$mailer = new Mailer();
$userManager = new UserManager($mailer);
相反,我們可以讓PHP-DI找出依賴關系:
$userManager = $container->get('UserManager');
在後台,PHP-DI將建立一個Mailer物件和一個UserManager物件。
它怎麽知道要註入什麽?容器使用一種稱為自動裝配的技術。這不是PHP-DI獨有的,但這仍然很棒。它將掃描程式碼並檢視建構函式中需要哪些參數。
在我們的範例中,
UserManager
建構函式接受一個
Mailer
物件:PHP-DI知道它需要建立一個物件。非常簡單,但非常有效。
webman 框架套用
在webman裏依賴自動註入是可選功能,此功能預設關閉。如果你需要依賴自動註入,推薦使用php-di,以下是webman結合php-di的用法。
安裝
composer require psr/container ^1.1.1 php-di/php-di ^6 doctrine/annotations ^1.14
修改配置
config/container.php
,其最終內容如下:
$builder = new \DI\ContainerBuilder();
$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
namespaceapp\service;
classMailer
{
publicfunctionmail($email, $content)
{
// 發送信件程式碼省略
}
}
app/controller/UserController.php
內容如下:
<?php
namespaceapp\controller;
usesupport\Request;
useapp\service\Mailer;
classUserController
{
private $mailer;
publicfunction__construct(Mailer $mailer)
{
$this->mailer = $mailer;
}
publicfunctionregister(Request $request)
{
$this->mailer->mail('[email protected]', 'Hello and welcome!');
return response('ok');
}
}
正常情況下,需要以下程式碼才能完成
app\controller\UserController
的例項化:
$mailer = new Mailer;
$user = new UserController($mailer);
當使用
php-di
後,開發者無需手動例項化控制器中的
Mailer
,webman會自動幫你完成。如果在例項化
Mailer
過程中有其它類的依賴,webman也會自動例項化並註入。開發者不需要任何的初始化工作。
註意
必須是由框架或者
php-di
建立的例項才能完成依賴自動註入,手動
new
的例項無法完成依賴自動註入,如需註入,需要使用
support\Container
介面替換
new
語句,例如:
useapp\service\UserService;
useapp\service\LogService;
usesupport\Container;
// 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]);
註解註入
除了建構函式依賴自動註入,我們還可以使用註解註入。繼續上面的例子,
app\controller\UserController
更改成如下:
<?php
namespaceapp\controller;
usesupport\Request;
useapp\service\Mailer;
useDI\Annotation\Inject;
classUserController
{
/**
* @Inject
* @var Mailer
*/
private $mailer;
publicfunctionregister(Request $request)
{
$this->mailer->mail('[email protected]', 'Hello and welcome!');
return response('ok');
}
}
這個例子透過
@Inject
註解註入,並且由
@var
註解聲明物件型別。這個例子和建構函式註入效果一樣,但是程式碼更精簡。
註意 webman在1.4.6版本之前不支持控制器參數註入,例如以下程式碼當webman<=1.4.6時是不支持的
<?php
namespaceapp\controller;
usesupport\Request;
useapp\service\Mailer;
classUserController
{
// 1.4.6版本之前不支持控制器參數註入
publicfunctionregister(Request $request, Mailer $mailer)
{
$mailer->mail('[email protected]', 'Hello and welcome!');
return response('ok');
}
}
自訂建構函式註入
有時候建構函式傳入的參數可能不是類的例項,而是字串、數位、陣列等數據。例如Mailer建構函式需要傳遞smtp伺服器ip和埠:
<?php
namespaceapp\service;
classMailer
{
private $smtpHost;
private $smtpPort;
publicfunction__construct($smtp_host, $smtp_port)
{
$this->smtpHost = $smtp_host;
$this->smtpPort = $smtp_port;
}
publicfunctionmail($email, $content)
{
// 發送信件程式碼省略
}
}
這種情況無法直接使用前面介紹的建構函式自動註入,因為
php-di
無法確定
$smtp_host
$smtp_port
的值是什麽。這時候可以嘗試自訂註入。
在
config/dependence.php
(檔不存在請自行建立)中加入如下程式碼:
return [
// ... 這裏忽略了其它配置
app\service\Mailer:: class => new app\service\Mailer('192.168.1.11', 25);
];
這樣當依賴註入需要獲取
app\service\Mailer
例項時將自動使用這個配置中建立的
app\service\Mailer
例項。
我們註意到,
config/dependence.php
中使用了
new
來例項化
Mailer
類,這個在本範例沒有任何問題,但是想象下如果
Mailer
類依賴了其它類的話或者
Mailer
類內部使用了註解註入,使用
new
初始化將不會依賴自動註入。解決辦法是利用自訂介面註入,透過
Container::get(類名)
或者
Container::make(類名, [建構函式參數])
方法來初始化類。