我們知道, Container
有很多種 「繫結」 的姿勢,比如 bind()
, extend()
, singleton()
, instance()
等等,那麼 Laravel
中怎樣「註冊」這些「繫結」呢?那就是 Service Provider
。
先看下 Laravel
文件中這句話:
Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers.
Service Providers (服務提供者)
是 Laravel
「引導」過程的核心。這個「引導」過程可以理解成「電腦從按下開機按鈕到完全進入桌面」這段時間系統乾的事。
概覽
Service Provider
有兩個重要的方法:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 註冊服務.
*
* @return void
*/
public function register()
{
//
}
/**
* 引導服務。
*
* @return void
*/
public function boot()
{
//
}
}
Laravel
在「引導」過程中幹了兩件重要的事:
- 通過
Service Provider
的register()
方法註冊「繫結」 - 所有
Servier Provider
的register()
都執行完之後,再通過它們boot()
方法,幹一些別的事。
過程分析
這個「先後順序」可以在 Laravel
的啟動過程中找到:
-
首先,生成核心
Container
:$app
(例項化過程中還註冊了一大堆基本的「繫結])。\public/index.php
:/* |-------------------------------------------------------------------------- | Turn On The Lights |-------------------------------------------------------------------------- | */ $app = require_once __DIR__.'/../bootstrap/app.php';
// bootstrap/app.php
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
-
接下來註冊
Http\Kernel
,Console\Kernel
,Debug\ExecptionHandler
三個「單例」繫結。\bootstrap/app.php
:$app->singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class );
-
然後「啟動」應用。\
public/index.php
:/* |-------------------------------------------------------------------------- | Run The Application |-------------------------------------------------------------------------- | */ $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); $response = $kernel->handle( $request = Illuminate\Http\Request::capture() );
-
由於以前的「繫結」,
$kernel
獲取的其實是App\Http\Kernel
類的例項,App\Http\Kernel
類又繼承了Illuminate\Foundation\Http\Kernel
類。\
其handle()
方法執行了sendRequestThroughRouter()
方法:
// Illuminate\Foundation\Http
class Kernel implements KernelContract
{
public function handle($request)
try {
//...
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
//...
}
}
}
-
這個
sendRequestThroughRouter()
方法執行了一系列bootstrappers (引導器)
:// Illuminate\Foundation\Http class Kernel implements KernelContract { protected function sendRequestThroughRouter($request) { //... // 按順序執行每個 bootstrapper $this->bootstrap(); //... } }
-
bootstrap()
方法中又呼叫了Illuminate\Foundation\Application
類的bootstrapWith()
方法:
// Illuminate\Foundation\Http
class Kernel implements KernelContract
{
public function bootstrap()
{
if (! $this->app->hasBeenBootstrapped()) {
//
$this->app->bootstrapWith($this->bootstrappers());
}
}
}
// Illuminate\Foundation\Http\Kernel
class Kernel implements KernelContract
{
protected $bootstrappers = [
//...
\Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 註冊 Providers
\Illuminate\Foundation\Bootstrap\BootProviders::class, // 引導 Providers
];
protected function bootstrappers()
{
return $this->bootstrappers;
}
}
這裡就能看出來 Service Provider
中 register
和 boot
的「先後順序了」。
後續的執行過程暫時先不介紹了。
這就意味著,在 Service Provider
boot
之前,已經把註冊好了所有服務的「繫結」。因此, 在 boot()
方法中可以使用任何已註冊的服務。\
例如:
{
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
public function boot()
{
// 這裡使用 make() 方法,可以更直觀
$this->app->make('view')->composer('view', function () {
//
});
// 或者使用 view() 輔助函式
view()->composer('view', function () {
//
});
}
}
如果瞭解 Container
「繫結」 和 make
的概念,應該很容易理解上面的過程。
剩餘的無非就是:
建立 Service Provier
php artisan make:provider MyServiceProvider
註冊繫結
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class MyServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(MyInterface::class, MyClass::class);
}
}
引導方法
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Routing\ResponseFactory;
class MyServiceProvider extends ServiceProvider
{
/**
* boot() 方法中可以使用依賴注入。
* 這是因為在 Illuminate\Foundation\Application 類中,
* 通過 bootProvider() 方法中的 $this->call([$provider, 'boot'])
* 來執行 Service Provider 的 boot() 方法
* Container 的 call() 方法作用,可以參考上一篇文章
*/
public function boot(ResponseFactory $response)
{
$response->macro('caps', function ($value) {
//
});
}
}
在 config/app.php
中註冊 Service Provider
'providers' => [
/*
* Laravel Framework Service Providers...
*/
/*
* Package Service Providers...
*/
/*
* Application Service Providers...
*/
App\Providers\MyServiceProvider::class,
],
注意:
Laravel 5.5
之後有 package discovery 功能的package
不需要在config/app.php
中註冊。
延時載入
按需載入,只有當 Container
「make」 ServiceProvider
類 providers()
方法中返回的值時,才會載入此 ServiceProvider
。\
例如:
namespace Illuminate\Hashing;
use Illuminate\Support\ServiceProvider;
class HashServiceProvider extends ServiceProvider
{
/**
* 如果延時載入,$defer 必須設定為 true 。
*
* @var bool
*/
protected $defer = true;
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->app->singleton('hash', function () {
return new BcryptHasher;
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return ['hash'];
}
}
當我們「make」時,才會註冊 HashServiceProvider
,即執行它的 register()
方法,進行 hash
的繫結:
class MyController extends Controller
{
public function test()
{
// 此時才會註冊 `HashServiceProvider`
$hash = $this->app->make('hash');
$hash->make('teststring');
// 或
\Hash::make('teststring');
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結