前言
本文 GitBook 地址: https://www.gitbook.com/book/leoyang90/lar...
服務提供者是 laravel
框架的重要組成部分,承載著各種服務,自定義的應用以及所有 Laravel
的核心服務都是通過服務提供者啟動。本文將會介紹服務提供者的原始碼分析,關於服務提供者的使用,請參考官方文件 :服務提供者。
服務提供者的註冊
服務提供者的啟動由類 \Illuminate\Foundation\Bootstrap\RegisterProviders::class
負責,該類用於載入所有服務提供者的 register
函式,並儲存延遲載入的服務的資訊,以便實現延遲載入。
class RegisterProviders
{
public function bootstrap(Application $app)
{
$app->registerConfiguredProviders();
}
}
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
public function registerConfiguredProviders()
{
(new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
->load($this->config['app.providers']);
}
public function getCachedServicesPath()
{
return $this->bootstrapPath().'/cache/services.php';
}
}
以上可以看出,所有服務提供者都在配置檔案 app.php
檔案的 providers
陣列中。類 ProviderRepository
負責所有的服務載入功能:
class ProviderRepository
{
public function load(array $providers)
{
$manifest = $this->loadManifest();
if ($this->shouldRecompile($manifest, $providers)) {
$manifest = $this->compileManifest($providers);
}
foreach ($manifest['when'] as $provider => $events) {
$this->registerLoadEvents($provider, $events);
}
foreach ($manifest['eager'] as $provider) {
$this->app->register($provider);
}
$this->app->addDeferredServices($manifest['deferred']);
}
}
載入服務快取檔案
laravel
會把所有的服務整理起來,作為快取寫在快取檔案中:
return array (
'providers' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
...
),
'eager' =>
array (
0 => 'Illuminate\\Auth\\AuthServiceProvider',
1 => 'Illuminate\\Cookie\\CookieServiceProvider',
...
),
'deferred' =>
array (
'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider',
...
),
'when' =>
array (
'Illuminate\\Broadcasting\\BroadcastServiceProvider' =>
array (
),
...
),
- 快取檔案中
providers
放入了所有自定義和框架核心的服務。 eager
陣列中放入了所有需要立即啟動的服務提供者。deferred
陣列中放入了所有需要延遲載入的服務提供者。when
放入了延遲載入需要啟用的事件。
載入服務提供者快取檔案:
public function loadManifest()
{
if ($this->files->exists($this->manifestPath)) {
$manifest = $this->files->getRequire($this->manifestPath);
if ($manifest) {
return array_merge(['when' => []], $manifest);
}
}
}
編譯服務提供者
若 laravel
中的服務提供者沒有快取檔案或者有變動,那麼就會重新生成快取檔案:
public function shouldRecompile($manifest, $providers)
{
return is_null($manifest) || $manifest['providers'] != $providers;
}
protected function compileManifest($providers)
{
$manifest = $this->freshManifest($providers);
foreach ($providers as $provider) {
$instance = $this->createProvider($provider);
if ($instance->isDeferred()) {
foreach ($instance->provides() as $service) {
$manifest['deferred'][$service] = $provider;
}
$manifest['when'][$provider] = $instance->when();
}
else {
$manifest['eager'][] = $provider;
}
}
return $this->writeManifest($manifest);
}
protected function freshManifest(array $providers)
{
return ['providers' => $providers, 'eager' => [], 'deferred' => []];
}
- 如果服務提供者是需要立即註冊的,那麼將會放入快取檔案中
eager
陣列中。 - 如果服務提供者是延遲載入的,那麼其函式
provides()
通常會提供服務別名,這個服務別名通常是向服務容器中註冊的別名,別名將會放入快取檔案的deferred
陣列中。 - 延遲載入若有
event
事件啟用,那麼可以在when
函式中寫入事件類,並寫入快取檔案的when
陣列中。
延遲服務提供者事件註冊
延遲服務提供者除了利用 IOC
容器解析服務方式啟用,還可以利用 Event
事件來啟用:
protected function registerLoadEvents($provider, array $events)
{
if (count($events) < 1) {
return;
}
$this->app->make('events')->listen($events, function () use ($provider) {
$this->app->register($provider);
});
}
註冊即時啟動的服務提供者
服務提供者的註冊函式 register()
由類 Application
來呼叫:
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
public function register($provider, $options = [], $force = false)
{
if (($registered = $this->getProvider($provider)) && ! $force) {
return $registered;
}
if (is_string($provider)) {
$provider = $this->resolveProvider($provider);
}
if (method_exists($provider, 'register')) {
$provider->register();
}
$this->markAsRegistered($provider);
if ($this->booted) {
$this->bootProvider($provider);
}
return $provider;
}
public function getProvider($provider)
{
$name = is_string($provider) ? $provider : get_class($provider);
return Arr::first($this->serviceProviders, function ($value) use ($name) {
return $value instanceof $name;
});
}
public function resolveProvider($provider)
{
return new $provider($this);
}
protected function markAsRegistered($provider)
{
$this->serviceProviders[] = $provider;
$this->loadedProviders[get_class($provider)] = true;
}
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
}
可以看出,服務提供者的註冊過程:
- 判斷當前服務提供者是否被註冊過,如註冊過直接返回物件
- 解析服務提供者
- 呼叫服務提供者的
register
函式 - 標記當前服務提供者已經註冊完畢
- 若框架已經載入註冊完畢所有的服務容器,那麼就啟動服務提供者的
boot
函式,該函式由於是call
呼叫,所以支援依賴注入。
延遲服務提供者啟用與註冊
延遲服務提供者首先需要新增到 Application
中:
public function addDeferredServices(array $services)
{
$this->deferredServices = array_merge($this->deferredServices, $services);
}
我們之前說過,延遲服務提供者的啟用註冊有兩種方法:事件與服務解析。
當特定的事件被激發後,就會呼叫 Application
的 register
函式,進而呼叫服務提供者的 register
函式,實現服務的註冊。
當利用 Ioc
容器解析服務名時,例如解析服務名 BroadcastingFactory
:
class BroadcastServiceProvider extends ServiceProvider
{
protected $defer = true;
public function provides()
{
return [
BroadcastManager::class,
BroadcastingFactory::class,
BroadcasterContract::class,
];
}
}
public function make($abstract)
{
$abstract = $this->getAlias($abstract);
if (isset($this->deferredServices[$abstract])) {
$this->loadDeferredProvider($abstract);
}
return parent::make($abstract);
}
public function loadDeferredProvider($service)
{
if (! isset($this->deferredServices[$service])) {
return;
}
$provider = $this->deferredServices[$service];
if (! isset($this->loadedProviders[$provider])) {
$this->registerDeferredProvider($provider, $service);
}
}
由 deferredServices
陣列可以得知,BroadcastingFactory
為延遲服務,接著程式會利用函式 loadDeferredProvider
來載入延遲服務提供者,呼叫服務提供者的 register
函式,若當前的框架還未註冊完全部服務。那麼將會放入服務啟動的回撥函式中,以待服務啟動時呼叫:
public function registerDeferredProvider($provider, $service = null)
{
if ($service) {
unset($this->deferredServices[$service]);
}
$this->register($instance = new $provider($this));
if (! $this->booted) {
$this->booting(function () use ($instance) {
$this->bootProvider($instance);
});
}
}
關於服務提供者的註冊函式:
class BroadcastServiceProvider extends ServiceProvider
{
protected $defer = true;
public function register()
{
$this->app->singleton(BroadcastManager::class, function ($app) {
return new BroadcastManager($app);
});
$this->app->singleton(BroadcasterContract::class, function ($app) {
return $app->make(BroadcastManager::class)->connection();
});
$this->app->alias(
BroadcastManager::class, BroadcastingFactory::class
);
}
public function provides()
{
return [
BroadcastManager::class,
BroadcastingFactory::class,
BroadcasterContract::class,
];
}
}
函式 register
為類 BroadcastingFactory
向 Ioc
容器繫結了特定的實現類 BroadcastManager
,這樣 Ioc
容器中的 make
函式:
public function make($abstract)
{
$abstract = $this->getAlias($abstract);
if (isset($this->deferredServices[$abstract])) {
$this->loadDeferredProvider($abstract);
}
return parent::make($abstract);
}
parent::make($abstract)
就會正確的解析服務 BroadcastingFactory
。
因此函式 provides()
返回的元素一定都是 register()
向 IOC
容器中繫結的類名或者別名。這樣當我們利用服務容器來利用 App::make()
解析這些類名的時候,服務容器才會根據服務提供者的 register()
函式中繫結的實現類,從而正確解析服務功能。
服務容器的啟動
服務容器的啟動由類 \Illuminate\Foundation\Bootstrap\BootProviders::class
負責:
class BootProviders
{
public function bootstrap(Application $app)
{
$app->boot();
}
}
class Application extends Container implements ApplicationContract, HttpKernelInterface
{
public function boot()
{
if ($this->booted) {
return;
}
$this->fireAppCallbacks($this->bootingCallbacks);
array_walk($this->serviceProviders, function ($p) {
$this->bootProvider($p);
});
$this->booted = true;
$this->fireAppCallbacks($this->bootedCallbacks);
}
protected function bootProvider(ServiceProvider $provider)
{
if (method_exists($provider, 'boot')) {
return $this->call([$provider, 'boot']);
}
}
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結