服務容器
- 服務綁定
- 基礎綁定
- ##簡單綁定 自動注入
- ##
服務容器
」容器事件#PSR-11##簡介Laravel 服務容器是用於管理類別的依賴和執行依賴注入的強大工具。依賴注入這個花俏名詞實質上是指:類別的依賴透過建構函數,或在某些情況下透過 "setter" 方法 "注入" 到類別。 來看一個簡單的範例:
在這個範例中,控制器 <?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;
}
/**
* 显示指定用户的 profile.
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
UserController
需要從資料來源取得 users。因此,我們要
一個能夠取得 users 的服務。在當前上下文中,我們的
UserRepository很可能是使用 Eloquent 從資料庫中取得 user 資訊。然而,由於 repository 是注入的,所以我們可以輕易地將其切換為另一個的實作。這種注入方式的便利之處也體現在當我們為應用程式編寫測試時,我們也可以輕鬆地 "模擬" 或建立 UserRepository 的虛擬實作。
想要建立強大的大型應用,至關重要的一件事是:要深刻地理解 Laravel 服務容器。當然,為 Laravel 的核心程式碼做出貢獻也是一樣。
服務綁定
#基礎綁定
由於大多數用戶的服務容器都被註冊在服務提供者中,所以下面的大多數的例子都是在演示服務提供者中使用容器。
{tip} 如果某個容器不依賴任何介面就沒必要去綁定類別在這個容器裡。容器不需要指定如何建構這些對象,因為它可以使用反射來自動解析這些對象。
簡單綁定
在服務提供者中,你總是可以透過
$this->app
# 屬性存取容器。我們可以透過容器的
方法註冊綁定,
bind方法的第一個參數為要綁定的類別/ 介面名,第二個參數是一個傳回類別實例的
Closure :
注意,我們接受容器本身作為解析器的參數。然後,我們可以使用容器來解析正在建構的物件的子依賴。 $this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
$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);
綁定基本值
當你有一個類別不僅需要接受一個注入類,還需要注入一個基本值(例如整數)。你可以使用上下文綁定來輕鬆注入你的類別所需的任何值:
$this->app->when('App\Http\Controllers\UserController') ->needs('$variableName') ->give($value);
綁定介面到實作
服務容器有一個很強大的功能,就是支援綁定介面到給定的實作。例如,如果我們有個 EventPusher
介面 和一個 RedisEventPusher
實作。一旦我們寫完了EventPusher
介面的RedisEventPusher
實現,我們就可以在服務容器中註冊它,像這樣:
$this->app->bind( 'App\Contracts\EventPusher', 'App\Services\RedisEventPusher' );
這麼做相當於告訴容器:當一個類別需要實作EventPusher
時,應該注入RedisEventPusher
。現在我們就可以在建構函式或任何其他透過服務容器注入依賴項的地方使用型別提示注入EventPusher
介面:
use App\Contracts\EventPusher; /** * Create a new class instance. * * @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\PhotoController; use App\Http\Controllers\VideoController; use Illuminate\Contracts\Filesystem\Filesystem; $this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); }); $this->app->when([VideoController::class, UploadController::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')); });
擴充綁定
extend
方法可以修改已解析的服務。例如,當一個服務被解析後,你可以加入額外的程式碼來修飾或配置它。 extend
方法接受一個閉包,該閉包唯一的參數就是這個服務, 並傳回修改過的服務:
$this->app->extend(Service::class, function ($service) { return new DecoratedService($service); });
解析實例
make
方法
你可以使用make
方法從容器中解析出類實例。 make
方法接收你想要解析的類別或介面的名字:
$api = $this->app->make('HelpSpot\API');
如果你的程式碼處於無法存取$app
變數的位置,則可用全域輔助函數resolve
來解析:
$api = resolve('HelpSpot\API');
如果類別依賴不能透過容器解析,你可以透過將它們作為關聯數組作為makeWith
方法的參數注入:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);#
自動注入
另外,更重要的是,你可以簡單地使用 "類型提示" 的方式在類別的建構函式中註入那些需要容器解析的依賴項,包括控制器,事件監聽器, 佇列任務,中間件,等。實際上,這才是大多數物件應該被容器解析的方式。
例如,你可以在控制器的建構子中加入一個repository 的型別提示,然後這個repository 將會被自動解析並注入類別中:
<?php namespace App\Http\Controllers; use App\Users\Repository as UserRepository; class UserController extends Controller{ /** * The user repository instance. */ protected $users; /** * Create a new controller instance. * * @param UserRepository $users * @return void */ public function __construct(UserRepository $users) { $this->users = $users; } /** * Show the user with the given 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"... });
正如你所看到的,被解析的物件將會被傳入回調函數,這使得你能夠在物件被傳給呼叫者之前給它設定額外的屬性。
PSR-11
Laravel 的服務容器實作了 PSR-11 介面。因此,你可以使用 PSR-11 容器 『介面類型提示』 來取得 Laravel 容器的實例:
use Psr\Container\ContainerInterface; Route::get('/', function (ContainerInterface $container) { $service = $container->get('Service'); // });
如果無法解析給定的標識符,則會引發例外狀況。未綁定識別碼時,會拋出 Psr\Container\NotFoundExceptionInterface
例外。如果識別碼已綁定但無法解析,會拋出 Psr\Container\ContainerExceptionInterface
例外。