## 容器和依賴注入
ThinkPHP使用容器來更方便的管理類依賴及運行依賴注入,新版的容器支持`PSR-11`規范。
>[danger] 容器類的工作由`think\Container`類完成,但大多數情況我們只需要通過`app`助手函數或者`think\App`類即可容器操作,如果在服務類中可以直接調用`this->app`進行容器操作。
依賴注入其實本質上是指對類的依賴通過構造器完成自動注入,例如在控制器架構方法和操作方法中一旦對參數進行對象類型約束則會自動觸發依賴注入,由于訪問控制器的參數都來自于URL請求,普通變量就是通過參數綁定自動獲取,對象變量則是通過依賴注入生成。
~~~
<?php
namespace app\controller;
use think\Request;
class Index
{
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function hello($name)
{
return 'Hello,' . $name . '!This is '. $this->request->action();
}
}
~~~
>[danger] 依賴注入的對象參數支持多個,并且和順序無關。
支持使用依賴注入的場景包括(但不限于):
* 控制器架構方法;
* 控制器操作方法;
* 路由的閉包定義;
* 事件類的執行方法;
* 中間件的執行方法;
對于自定義的類以及方法,如果需要使用依賴注入,需要使用系統提供的`invoke`助手函數調用,例如:
```
class Foo
{
public function __construct(Bar $bar)
{
}
}
```
如果直接`new`的話,需要手動傳入`Bar`對象實例
```
$bar = new Bar();
$foo = new Foo($bar);
```
如果使用容器來實例化的話,可以自動進行依賴注入。
```
$foo = invoke('Foo');
```
如果要對某個方法支持依賴注入,可以使用
```
class Foo
{
public function bar(Bar $bar)
{
// ...
}
}
```
```
$result = invoke(['Foo', 'bar']);
```
也支持對某個函數或者閉包使用依賴注入
```
$result = invoke(function(Bar $bar) {
// ...
});
```
## 綁定
依賴注入的類統一由容器進行管理,**大多數情況下是在自動綁定并且實例化的**。不過你可以隨時進行手動綁定類到容器中(通常是在服務類的`register`方法中進行綁定),支持多種綁定方式。
### 綁定類標識
可以對已有的類庫綁定一個標識(唯一),便于快速調用。
~~~php
// 綁定類庫標識
$this->app->bind('think\Cache', 'app\common\Cache');
~~~
或者使用助手函數
~~~php
// 綁定類庫標識
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!');
}
}
~~~
### 批量綁定
在實際應用開發過程,不需要手動綁定,我們只需要在`app`目錄下面定義`provider.php`文件(只能在全局定義,不支持應用單獨定義),系統會自動批量綁定類庫到容器中。
~~~
return [
'route' => \think\Route::class,
'session' => \think\Session::class,
'url' => \think\Url::class,
];
~~~
>[danger] 綁定標識調用的時候區分大小寫,系統已經內置綁定了核心常用類庫,無需重復綁定
系統內置綁定到容器中的類庫包括
| 系統類庫 | 容器綁定標識 |
| --- | --- |
| think\\App | app |
| think\\Cache | cache |
| think\\Config | config |
| think\\Cookie | cookie |
| think\\Console | console |
| think\\Db | db |
| think\\Debug | debug |
| think\\Env | env |
| think\\Event | event |
| think\\Http | http |
| think\\Lang | lang |
| think\\Log | log |
| think\\Middleware | middleware |
| think\\Request | request |
| think\\Response | response |
| think\\Filesystem| filesystem |
| think\\Route | route |
| think\\Session | session |
| think\\Validate | validate |
| think\\View | view |
## 解析
使用`app`助手函數進行容器中的類解析調用,對于已經綁定的類標識,會自動快速實例化
~~~
$cache = app('cache');
~~~
帶參數實例化調用
~~~
$cache = app('cache',['file']);
~~~
對于沒有綁定的類,也可以直接解析
~~~
$arrayItem = app('org\utils\ArrayItem');
~~~
>[danger] 調用和綁定的標識必須保持一致(包括大小寫)
容器中已經調用過的類會自動使用單例,除非你使用下面的方式強制重新實例化。
~~~
// 每次調用都會重新實例化
$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) {
// ...
});
```
- 序言
- 基礎
- 安裝
- 開發規范
- 目錄結構
- 配置
- 架構
- 請求流程
- 架構總覽
- 入口文件
- 多應用模式
- URL訪問
- 容器和依賴注入
- 服務
- 門面
- 中間件
- 事件
- 路由
- 路由定義
- 變量規則
- 路由地址
- 路由參數
- 路由中間件
- 路由分組
- 資源路由
- 注解路由
- 路由綁定
- 域名路由
- MISS路由
- 跨域請求
- URL生成
- 控制器
- 控制器定義
- 基礎控制器
- 空控制器
- 資源控制器
- 控制器中間件
- 請求
- 請求對象
- 請求信息
- 輸入變量
- 請求類型
- HTTP頭信息
- 偽靜態
- 參數綁定
- 請求緩存
- 響應
- 響應輸出
- 響應參數
- 重定向
- 文件下載
- 數據庫
- 連接數據庫
- 分布式數據庫
- 查詢構造器
- 查詢數據
- 添加數據
- 更新數據
- 刪除數據
- 查詢表達式
- 鏈式操作
- where
- table
- alias
- field
- strict
- limit
- page
- order
- group
- having
- join
- union
- distinct
- lock
- cache
- cacheAlways
- comment
- fetchSql
- force
- partition
- failException
- sequence
- replace
- extra
- duplicate
- procedure
- 聚合查詢
- 分頁查詢
- 時間查詢
- 高級查詢
- 視圖查詢
- JSON字段
- 子查詢
- 原生查詢
- 獲取查詢參數
- 查詢事件
- 獲取器
- 事務操作
- 存儲過程
- 數據集
- 數據庫驅動
- 模型
- 定義
- 模型字段
- 新增
- 更新
- 刪除
- 查詢
- 查詢范圍
- JSON字段
- 獲取器
- 修改器
- 搜索器
- 數據集
- 自動時間戳
- 只讀字段
- 軟刪除
- 類型轉換
- 模型輸出
- 模型事件
- 模型關聯
- 一對一關聯
- 一對多關聯
- 遠程一對多
- 遠程一對一
- 多對多關聯
- 多態關聯
- 關聯預載入
- 關聯統計
- 關聯輸出
- 虛擬模型
- 視圖
- 模板變量
- 視圖過濾
- 模板渲染
- 模板引擎
- 視圖驅動
- 錯誤和日志
- 異常處理
- 日志處理
- 調試
- 調試模式
- Trace調試
- SQL調試
- 變量調試
- 遠程調試
- 驗證
- 驗證器
- 驗證規則
- 錯誤信息
- 驗證場景
- 路由驗證
- 內置規則
- 表單令牌
- 注解驗證
- 雜項
- 緩存
- Session
- Cookie
- 多語言
- 上傳
- 命令行
- 啟動內置服務器
- 查看版本
- 自動生成應用目錄
- 創建類庫文件
- 清除緩存文件
- 生成數據表字段緩存
- 生成路由映射緩存
- 輸出路由定義
- 自定義指令
- Debug輸出級別
- 擴展庫
- 數據庫遷移工具
- Workerman
- think助手工具庫
- 驗證碼
- Swoole
- 附錄
- 助手函數
- 升級指導
- 更新日志