Laravel 服務容器是一個用於管理類依賴和執行依賴注入的強大工具。依賴注入聽上去很花哨,其實質是經過構造函數或者某些狀況下經過 set
方法將類依賴注入到類中。php
讓咱們看一個簡單的例子:laravel
<?php namespace App\Http\Controllers; use App\User; use App\Repositories\UserRepository; use App\Http\Controllers\Controller; class UserController extends Controller { /** * The user repository implementation. * * @var UserRepository */ protected $users; /** * Create a new controller instance. * * @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 從數據庫獲取用戶信息。注入 UserRepository
後,咱們能夠在其基礎上封裝其餘實現,也能夠模擬或者建立一個假的 UserRepository
實現用於測試。數據庫
深刻理解 Laravel 服務容器對於構建功能強大的大型 Laravel 應用而言相當重要,對於貢獻代碼到 Laravel 核心也頗有幫助。api
幾乎全部的服務容器綁定都是在服務提供者中完成。所以本章節的演示例子用到的容器都是在服務提供者中綁定。閉包
注:若是一個類沒有基於任何接口那麼就沒有必要將其綁定到容器。容器並不須要被告知如何構建對象,由於它會使用 PHP 的反射服務自動解析出具體的對象。app
簡單的綁定ide
在一個服務提供者中,能夠經過 $this->app
變量訪問容器,而後使用 bind
方法註冊一個綁定,該方法須要兩個參數,第一個參數是咱們想要註冊的類名或接口名稱,第二個參數是返回類的實例的閉包:函數
$this->app->bind('HelpSpot\API', function ($app) { return new HelpSpot\API($app->make('HttpClient')); });
注意到咱們將容器自己做爲解析器的一個參數,而後咱們可使用該容器來解析咱們正在構建的對象的子依賴。工具
綁定一個單例post
singleton
方法綁定一個只須要解析一次的類或接口到容器,而後接下來對容器的調用將會返回同一個實例:
$this->app->singleton('FooBar', function ($app) { return new FooBar($app->make('HttpClient')); });
綁定實例
你還可使用 instance
方法綁定一個已存在的對象實例到容器,隨後 調用 容器將老是返回給定的實例:
$api = new HelpSpot\API(new HttpClient); $this->app->instance('HelpSpot\Api', $api);
綁定原始值
你可能有一個接收注入類的類,同時須要注入一個原生的數值好比整型,能夠結合上下文輕鬆注入這個類須要的任何值:
$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')); });
有不少方式能夠從容器中解析對象,首先,你可使用 make
方法,該方法接收你想要解析的類名或接口名做爲參數:
$fooBar = $this->app->make('HelpSpot\API');
若是你所在的代碼位置訪問不了$app
變量,可使用輔助函數app
:
$api = app('HelpSpot\API');
最後,也是最經常使用的,你能夠簡單的經過在類的構造函數中對依賴進行類型提示來從容器中解析對象,控制器、事件監聽器、隊列任務、中間件等都是經過這種方式。在實踐中,這是大多數對象從容器中解析的方式。
容器會自動爲其解析類注入依賴,例如,你能夠在控制器的構造函數中爲應用定義的倉庫進行類型提示,該倉庫會自動解析並注入該類:
<?php namespace App\Http\Controllers; use Illuminate\Routing\Controller; 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"... });
正如你所看到的,被解析的對象將會傳遞給回調函數,從而容許你在對象被傳遞給消費者以前爲其設置額外屬性。