Laravel 核心--服務容器

簡介

服務容器就是一個普通的容器,用來封裝類的實例,然后在需要的時候再取出來。用更專業的術語來說是服務容器實現了控制反轉(Inversion of Control,縮寫為IoC),
意思是正常情況下類A需要一個類B的時候,我們需要自己去new類B,意味著我們必須知道類B的更多細節,比如構造函數,隨著項目的復雜性增大,這種依賴是毀滅性的。控制反轉的意思就是,將類A主動獲取類B的過程顛倒過來變成被動,類A只需要聲明它需要什么,然后由容器提供。

想象一下

樣做的好處是,類A不依賴于類B的實現,這樣在一定程度上解決了耦合問題。

在Laravel的服務容器中,為了實現控制反轉,可以有以下兩種:

  1. 依賴注入(Dependency Injection)。
  2. 綁定。

依賴注入

依賴注入是一種類型提示,舉官網的例子

<?php

namespace App\Http\Controllers;

use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;

class UserController extends Controller
{
    /**
     * 用于獲取用戶數據
     *
     * @var UserRepository
     */
    protected $users;

    /**
     * 構造方法,聲明所需要的類型
     * 
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * Show the profile for the given user.
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        $user = $this->users->find($id);
        return view('user.profile', ['user' => $user]);
    }
}

在本例中,UserController 需要從數據源獲取用戶,所以,我們注入了一個可以獲取用戶的服務 UserRepository,其扮演的角色類似使用 Eloquent 從數據庫獲取用戶信息。
這里UserController需要一個UserRepository實例,我們只需在構造方法中聲明我們需要的類型,容器在實例化UserController時會自動生成UserRepository的實例(或者實現類,因為UserRepository可以為接口),而不用主動去獲取UserRepository的實例,這樣也就避免了了解UserRepository的更多細節,也不用解決UserRepository所產生的依賴,我們所做的僅僅是聲明我們所需要的類型,所有的依賴問題都交給容器去解決。

綁定

幾乎所有的服務容器綁定都是在服務提供者中完成。

注:如果一個類沒有基于任何接口那么就沒有必要將其綁定到容器。容器并不需要被告知如何構建對象,因為它會使用 PHP 的反射服務自動解析出具體的對象。

綁定操作一般在 服務提供者 ServiceProviders中的register方法中,最基本的綁定是容器的bind方法,它接受一個類的別名或者全名和一個閉包來獲取實例:

簡單的綁定

在一個服務提供者中,可以通過 $this->app 變量訪問容器,然后使用 bind 方法注冊一個綁定,該方法需要兩個參數,第一個參數是我們想要注冊的類名或接口名稱,第二個參數是返回類的實例的閉包:

$this->app->bind('blogConfig', function ($app) {
    return new MapRepository();
});
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

綁定一個單例

singleton方法和bind寫法沒什么區別,singleton方法綁定一個只需要解析一次的類或接口到容器,然后接下來對容器的調用將會返回同一個實例:

$this->app->singleton('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app->make('HttpClient'));
});

綁定實例

你還可以使用 instance 方法綁定一個已存在的對象實例到容器,隨后調用容器將總是返回給定的實例:

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);

上文中提到的request實例就是通過這種方法綁定到容器的:

    $this->app->instance('request', $request);

綁定原始值

你可能有一個接收注入類的類,同時需要注入一個原生的數值比如整型,可以結合上下文輕松注入這個類需要的任何值:

$this->app->when('App\Http\Controllers\UserController')
    ->needs('$variableName')
    ->give($value);

綁定接口到實現

服務容器的一個非常強大的功能是其綁定接口到實現。我們假設有一個 EventPusher 接口及其實現類RedisEventPusher ,編寫完該接口的 RedisEventPusher 實現后,就可以將其注冊到服務容器:

$this->app->bind(
    'App\Contracts\EventPusher', 
    'App\Services\RedisEventPusher'
);

這段代碼告訴容器當一個類需要 EventPusher 的實現時將會注入RedisEventPusher,現在我們可以在構造器或者任何其它通過服務容器注入依賴的地方進行 EventPusher 接口的依賴注入:

use App\Contracts\EventPusher;

/**
 * 創建一個新的類實例
 *
 * @param  EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher){
    $this->pusher = $pusher;
}

上下文綁定

有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現,例如,兩個控制器依賴 Illuminate\Contracts\Filesystem\Filesystem
契約 的不同實現。Laravel 為此定義了簡單、平滑的接口:

use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
use Illuminate\Contracts\Filesystem\Filesystem;

$this->app->when(PhotoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('local');
    });

$this->app->when(VideoController::class)
    ->needs(Filesystem::class)
    ->give(function () {
        return Storage::disk('s3');
    });

標簽

少數情況下,我們需要解析特定分類下的所有綁定,例如,你正在構建一個接收多個不同 Report 接口實現的報告聚合器,在注冊完 Report實現之后,可以通過 tag 方法給它們分配一個標簽:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

這些服務被打上標簽后,可以通過 tagged 方法來輕松解析它們:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

解析

也就是獲取綁定的實例:

獲取方法

1.  app('HelpSpot\API');

2.  app()->make('HelpSpot\API');

3.  app()['HelpSpot\API'];

4.  resolve('HelpSpot\API');

以上四種方法均會返回獲得HelpSpot\API的實例。
區別是在一次請求的生命周期中:
bind方法的閉包會在每一次調用以上四種方法時執行,
singleton方法的閉包只會執行一次。

在使用中,如果每一個類要獲的不同的實例,或者需要“個性化”的實例時,這時我們需要用bind方法以免這次的使用對下次的使用造成影響;

如果實例化一個類比較耗時或者類的方法不依賴該生成的上下文,那么我們可以使用singleton方法綁定。singleton方法綁定的好處就是,如果在一次請求中我們多次使用某個類,那么只生成該類的一個實例將節省時間和空間。

自動注入

最后,也是最常用的,你可以簡單的通過在類的構造函數中對依賴進行類型提示來從容器中解析對象,控制器事件監聽器隊列任務中間件等都是通過這種方式。在實踐中,這是大多數對象從容器中解析的方式。
容器會自動為其解析類注入依賴,例如,你可以在控制器的構造函數中為應用定義的倉庫進行類型提示,該倉庫會自動解析并注入該類:

<?php

namespace App\Http\Controllers;

use App\Users\Repository as UserRepository;

class UserController extends Controller{
    /**
     * 用戶倉庫實例
     */
    protected $users;

    /**
     * 創建一個控制器實例
     *
     * @param  UserRepository  $users
     * @return void
     */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
     * 通過指定ID顯示用戶
     *
     * @param  int  $id
     * @return Response
     */
    public function show($id)
    {
        //
    }
}

容器事件

服務容器在每一次解析對象時都會觸發一個事件,可以使用 resolving 方法監聽該事件:

$this->app->resolving(function ($object, $app) {
    // Called when container resolves object of any type...
});

$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

正如你所看到的,被解析的對象將會傳遞給回調函數,從而允許你在對象被傳遞給消費者之前為其設置額外屬性。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,778評論 18 139
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,314評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,709評論 18 399
  • 開學第一課在9月1日晚上 9:00 準時播出,本次活動主題是《中華驕傲》,主持人董卿和撒貝寧,閃亮登場。主持人說:...
    好逗逼閱讀 155評論 0 1
  • 請想象一下你與深愛的人正躺在床上。你們相擁而臥,感覺安心、溫暖、充滿活力又無比的自信。恰當的時間恰當的地點,身邊是...
    嬌之語閱讀 1,071評論 0 1