容器和依賴注入



容器與依賴注入(container)

依賴注入, 是透過容器來實現的, 可以實現對引用的類別的自動引入, 以及該類別的依賴對象的自動加載

依賴注入是指對類別的依賴通過構造器完成自動注入:

<?php
namespace app\index\controller;

use app\index\model\User;

class Index
{
    protected $user;

    // 通过依赖注入方式,实现User实例的自动注入到当前对象中
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function hello()
    {
        return 'Hello,' . $this->user->name . '!';
    }
}

依賴注入的對象參數支持多個,且和順序無關。

綁定

依賴注入的類別統一由容器管理,大多數情況下是在自動綁定且實例化的。不過你可以隨時進行手動綁定類別到容器中(通常是在服務類別的register方法中進行綁定),支援多種綁定方式。

綁定類別標識

可以對已有的類別庫綁定一個標識(唯一),以便於快速呼叫。

// 绑定类库标识
$this->app->bind('cache', 'think\Cache');

或使用助手函數

// 绑定类库标识
bind('cache', 'think\Cache');

綁定的類別標識可以自己定義(只要不衝突)。

綁定閉包

可以綁定一個閉包到容器中

bind('sayHello', function ($name) {
    return 'hello,' . $name;
});

綁定實例

也可以直接綁定一個類別的實例

$cache = new think\Cache;
// 绑定类实例
bind('cache', $cache);

綁定到介面實作

對於依賴注入使用介面類別的情況,我們需要告訴系統使用哪個特定的介面實作類別來進行注入,這個使用可以把某個類別綁定到介面

// 绑定think\LoggerInterface接口实现到think\Log
bind('think\LoggerInterface','think\Log');

使用介面作為依賴注入的類型

<?php
namespace app\index\controller;

use think\LoggerInterface;

class Index
{
    public function hello(LoggerInterface $log)
    {
    	$log->record('hello,world!');
    }	
}

批次綁定

在實際應用開發過程,不需要手動綁定,我們只需要在應用程式目錄下面定義provider.php檔案(傳回一個陣列),系統會自動批次綁定類別庫到容器中。

return [
    'route'      => \think\Route::class,
    'session'    => \think\Session::class,
    'url'        => \think\Url::class,
];

綁定標識呼叫的時候區分大小寫,系統已經內建綁定了核心常用類別庫,無需重複綁定

系統內建綁定到容器中的類別庫包括

##request#think\Responseresponsethink\Filesystemfilesystem#think\Route routethink\Sessionsessionthink\Validate# validatethink\View
系統類別庫容器綁定識別碼
think\Appapp
think\Cachecache
think\Configconfig
think\Cookiecookie
#think\Consoleconsole
think\Consoleconsole
#think\Dbdb
think\Debugdebug
think \Envenv
think\Event#event
think\Httphttp
think\Langlang
#think\Loglog
think\Middlewaremiddleware
#think\Request
###view#############

解析

使用app助手函數進行容器中的類別解析調用,對於已經綁定的類別標識,會自動快速實例化

$cache = app('cache');

帶參數實例化呼叫

$cache = app('cache',['file']);

對於沒有綁定的類,也可以直接解析

$arrayItem = app('org\utils\ArrayItem');

呼叫和綁定的標識必須保持一致(包括大小寫)

容器中已經呼叫過的類別會自動使用單例,除非你使用下面的方式強制重新實例化。

// 每次调用都会重新实例化
$cache = app('cache', [], true);

物件化呼叫

使用app助理函數取得容器中的物件實例(支援依賴注入)。

$app = app();
// 判断对象实例是否存在
isset($app->cache);

// 注册容器对象实例
$app->cache = think\Cache::class;

// 获取容器中的对象实例
$cache = $app->cache;

也就是說,你可以在任何地方使用app()方法呼叫容器中的任何類,但大多數情況下面,我們更建議使用依賴注入。

// 调用配置类
app()->config->get('app_name');
// 调用session类
app()->session->get('user_name');

自動注入

容器主要用於依賴注入,依賴注入會先檢查容器中是否註冊過該物件實例,如果沒有就會自動實例化,然後自動注入,例如:

我們可以給路由綁定模型物件實例

Route::get('user/:id','index/Index/hello')
	->model('\app\index\model\User');

然後在操作方法中自動注入User模型

<?php
namespace app\index\controller;

use app\index\model\User;

class Index
{

    public function hello(User $user)
    {
        return 'Hello,'.$user->name;
    }

}

自訂實例化

容器中的物件實例化支援自訂,可以在你需要依賴注入的物件中增加__make方法定義,例如:

如果你希望User模型類別在依賴注入的時候使用自訂實例化的方式,可以用下面的方法。

<?php
namespace app\index\model;

use think\Model;
use think\db\Query;

class User extends Model
{
	public static function __make(Query $query)
    {
    	return (new self())->setQuery($query);
    }
}

容器物件回呼機制

容器中的物件實例化之後,支援回呼機制,利用該機制可以實現諸如註解功能等相關功能。

你可以透過resolving方法註冊一個全域回呼

Container::getInstance()->resolving(function($instance,$container) {
    // ...
});

回呼方法支援兩個參數,第一個參數是容器物件實例,第二個參數是容器實例本身。

或單獨註冊一個某個容器物件的回呼

Container::getInstance()->resolving(\think\Cache::class,function($instance,$container) {
    // ...
});


#