你的應用程序以及 Laravel 的所有核心服務都是通過服務提供器進行引導(注冊),服務提供器是配置你的應用程序的中心。
Laravel 的 config/app.php 文件中有一個 providers 數組。數組中的內容是應用程序要加載的所有服務提供器類。這其中有許多提供器並不會在每次請求時都被加載,只有當它們提供的服務實際需要時才會加載。這種我們稱之為「延遲」提供器,推遲加載這種提供器會提高應用程序的性能。
1. 編寫服務提供器
生成文件:
php artisan make:provider RiakServiceProvider
運行后會發現生成了app/Providers/RiakServiceProvider.php文件
生成文件內容:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
//
}
}
從以上文件可以看出:
- 所有服務提供器都會繼承 Illuminate\Support\ServiceProvider 類。
- 大多數服務提供器都包含 register 和 boot 方法
1.1 注冊方法 register
只是單純的綁定,不注冊任何時間的監聽器、路由、或者其它任何功能,否則你可能使用到未加載的服務
在你的任何服務提供器方法中,你可以通過 $app 屬性來訪問服務容器:
1.1.1 簡單綁定
傳遞我們想要注冊的類或接口名稱再返回類的實例的 Closure :
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
1.1.2 綁定單例
singleton 方法將類或接口綁定到只能解析一次的容器中。綁定的單例被解析后,相同的對象實例會在隨后的調用中返回到容器中:
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
1.1.3 綁定實例
你也可以使用 instance 方法將現有對象實例綁定到容器中。給定的實例會始終在隨后的調用中返回到容器中
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
1.1.4 綁定初始數據
當你有一個類不僅需要接受一個注入類,還需要注入一個基本值(比如整數)。你可以使用上下文綁定來輕松注入你的類需要的任何值:
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
1.2 引導方法 boot
此方法在所有其它服務提供者都注冊之后才調用,我們可以訪問所以已經被框架注冊的服務,比如我們想實現某個功能,比如中間件功能,事件監聽等,你甚至可以在這里注冊一條路由,這樣就可以是這條路由生效了
public function boot()
{
view()->composer('view', function () {
//
});
}
可以在boot方法使用依賴注入
use Illuminate\Contracts\Routing\ResponseFactory;
//使用了類型提示
public function boot(ResponseFactory $response)
{
$response->macro('caps', function ($value) {
//
});
}
2. 注冊服務提供器
創建好服務提供者之后需要在config/app.php配置文件中注冊,寫在providers數組中。
'providers' => [
// 其他服務提供器
App\Providers\ComposerServiceProvider::class,
],
3. 解析
3.1 make 方法
你可以使用 make 方法將容器中的類實例解析出來。make 方法接受要解析的類或接口的名稱:
$api = $this->app->make('HelpSpot\API');
如果你的代碼處於不能訪問 $app 變量的位置,你可以使用全局的輔助函數 resolve:
$api = resolve('HelpSpot\API');
如果你的某些類的依賴項不能通過容器去解析,那你可以通過將它們作為關聯數組傳遞到 makeWith 方法來注入它們。
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
3.2 自動注入
你可以簡單地使用「類型提示」的方式在由容器解析的類的構造函數中添加依賴項,包括 控制器、事件監聽器、隊列任務、中間件 等。 事實上,這是你的大多數對象也應該由容器解析。
例如,你可以在控制器的構造函數中對應用程序定義的 Repository 使用類型提示。Repository 會被自動解析並注入到類中:
<?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)
{
//
}
}
4. 綁定方法詳解
上面談到了注冊方法中的綁定,我們要指到服務容器有一個強大的功能,就是將接口綁定到給定實現。
4.1 綁定接口到實現
例如,如果我們有一個 EventPusher 接口和一個 RedisEventPusher 實現。編寫完接口的 RedisEventPusher 實現后,我們就可以在服務容器中注冊它,像這樣:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
使用也很簡單,可以在構造函數或者任何其他通過服務容器注入依賴項的地方使用類型提示注入 EventPusher 接口:
use App\Contracts\EventPusher;
/**
* 創建一個新的類實例
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
分析以上的代碼,當一個類需要實現 EventPusher 時,應該注入 RedisEventPusher,我們可能在更多的地方注入EventPusher,有一天我們想換成memcachedEventPusher,再想想,是不是發現了,我們不必去一個個修改每一個使用EventPusher的類,我們只需要在服務提供器這里修改就可以了。
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\memcachedEventPusher'
);
4.2 上下文綁定
有時候,你可能有兩個類使用了相同的接口,但你希望每個類都能注入不同的實現。例如,兩個控制器可能需要依賴不同的 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)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
4.3 標記
標記#
有時候,你可能需要解析某個「分類」下的所有綁定。例如,你正在構建一個報表的聚合器,它接收一個包含不同 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'));
});
5. 延遲提供器
推遲其注冊,提高性能,只有在嘗試解析其中的服務時候才會加載服務提供者。
要延遲提供器的加載,請將 defer 屬性設置為 true ,並定義 provides 方法。provides 方法應該返回由提供器注冊的服務容器綁定:
<?php
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider
{
//1.是否延時加載服務提供者
protected $defer = true;
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection($app['config']['riak']);
});
}
//2.定義provides方法,返回由提供器注冊的服務容器綁定
public function provides()
{
return [Connection::class];
}
}
6. 容器事件
在給消費者使用前,可以做最后一步監聽修改(Container Events)
每當服務容器解析一個對象時觸發一個事件。你可以使用 resolving 方法監聽這個事件:
$this->app->resolving(function ($object, $app) {
// 當容器解析任何類型的對象時調用...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// 當容器解析類型為「HelpSpot\API」的對象時調用...
});
如你所見,被解析的對象會被傳遞給回調中,讓你在對象被傳遞出去之前可以在對象上設置任何屬性。
