【Laravel-海賊王系列】第八章, Provider 功能解析

Jijilin發表於2019-02-23

開始

我們直接從 Kernelhandle() 方法中開始分析,handle() 負責處理了請求,所有框架的啟動也是在這裡開始!

protected function sendRequestThroughRouter($request)
{
   $this->app->instance('request', $request);

   Facade::clearResolvedInstance('request');

   // "這裡就是核心的引導方法,故事也是從這裡開始的。"
   $this->bootstrap(); 

   return (new Pipeline($this->app))
               ->send($request)
               ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
               ->then($this->dispatchToRouter());
}
複製程式碼

下面是核心引導啟動函式

$this->bootstrappers() = [
   \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
   \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
   \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
   \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
   
   // "找到了,它就是傳給 App 物件進行啟動的!"
   \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
   
   // "這是在所有 provider 註冊之後呼叫他們的 boot() 方法"
   \Illuminate\Foundation\Bootstrap\BootProviders::class,
]

public function bootstrap()
{
   if (! $this->app->hasBeenBootstrapped()) {
       $this->app->bootstrapWith($this->bootstrappers());
   }
}
複製程式碼

接著就是把這一組類傳給 Application 物件的 bootstrapWith() 方法,繼續下去

public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        
        // "啟動中觸發回撥函式"
        $this['events']->dispatch('bootstrapping: '.$bootstrapper, [$this]);

        // "這裡是我們要關注的功能。"
        $this->make($bootstrapper)->bootstrap($this);

        // "啟動完成後觸發回撥函式"
        $this['events']->dispatch('bootstrapped: '.$bootstrapper, [$this]);
    }
}
複製程式碼

$this->make($bootstrapper)->bootstrap($this) 這個方法是從容器解析出傳入的類,然後呼叫對應例項的 bootstrap($this) 方法

只需追蹤 \Illuminate\Foundation\Bootstrap\RegisterProviders::classbootstrap($this) 方法

public function bootstrap(Application $app)
{
    $app->registerConfiguredProviders();
}
複製程式碼

繼續看 ApplicationregisterConfiguredProviders()

public function registerConfiguredProviders()
{
    $providers = Collection::make($this->config['app.providers'])
                    ->partition(function ($provider) {
                        return Str::startsWith($provider, 'Illuminate\\');
                    });

    $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]); 

    (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath()))
                ->load($providers->collapse()->toArray());
}
複製程式碼

首先讀取 $this->config['app.providers'] 中的所有類名,以下是 laravel 框架預設的載入的服務提供者

'providers' => [

    Illuminate\Auth\AuthServiceProvider::class,
   
    ... 省略類似程式碼
   
    Illuminate\View\ViewServiceProvider::class,
    
    
    
    App\Providers\AppServiceProvider::class,
    
    ... 省略類似程式碼
    
    App\Providers\RouteServiceProvider::class,

],
複製程式碼

通過集合來操作,按照名稱空間來分割槽,這裡目前只有兩組 IlluminateApp 開頭的類

繼續分析 $providers->splice(1, 0, [$this->make(PackageManifest::class)->providers()]);

[$this->make(PackageManifest::class)->providers()] 這段程式碼是 Laravel 5.5 引入的包自動發現功能,

主要實現了從所有的包的 composer.json 檔案下讀取 laravel 下的 providers 中的內容。

【Laravel-海賊王系列】第八章, Provider 功能解析

在引入這個功能後很多包都不需要我們手動再去 app.providers 中去配置了。

在這行程式碼執行完成之後 $providers 會變成一個分成了三段的集合物件。

【Laravel-海賊王系列】第八章, Provider 功能解析

繼續看下去 (new ProviderRepository($this, new Filesystem, $this->getCachedServicesPath())) ->load($providers->collapse()->toArray());

public function __construct(ApplicationContract $app, Filesystem $files, $manifestPath)
{
    $this->app = $app;
    $this->files = $files;
    $this->manifestPath = $manifestPath;
}
複製程式碼

這裡首先例項化了一個 ProviderRepository 物件,這個物件需要三個引數

$app, new Filesystem$this->getCachedServicesPath()

這裡的 $this->getCachedServicesPath() 就是 bootstrap/cache/services.php 這個檔案。

這裡主要依靠 ProviderRepository 物件的 load() 來實現。

繼續追蹤 load() 方法

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']);
}
複製程式碼

展開 $manifest = $this->loadManifest();

public function loadManifest()
{
    if ($this->files->exists($this->manifestPath)) {
        $manifest = $this->files->getRequire($this->manifestPath);

        if ($manifest) {
            return array_merge(['when' => []], $manifest);
        }
    }
}
複製程式碼

可以看到如果存在剛才傳入的bootstrap/cache/services.php

這個檔案則直接載入,之後合併引數返回。

為了直觀,我們來看看 $manifest 的樣子

【Laravel-海賊王系列】第八章, Provider 功能解析

繼續看下一段,如果傳入的 $providers 和快取中取出來的結果不相同,則通過 $providers 重新構建快取

if ($this->shouldRecompile($manifest, $providers)) {
    $manifest = $this->compileManifest($providers);
} 
複製程式碼

繼續找下去,如果服務提供者的 when() 方法有返回事件則會在此處被監聽

foreach ($manifest['when'] as $provider => $events) {
    $this->registerLoadEvents($provider, $events);
}
複製程式碼

這裡的類表示不需要延遲載入,因此框架會直接開始載入這些類

foreach ($manifest['eager'] as $provider) {
    $this->app->register($provider);
}
複製程式碼

$this->app->addDeferredServices($manifest['deferred']); 最後一段就是延遲載入的功能了!

public function addDeferredServices(array $services)
{
    $this->deferredServices = array_merge($this->deferredServices, $services);
}
複製程式碼

這段程式碼就是將當前 $services 中的值合併到 $app 物件的 $deferredServices 成員中。

額外擴充,關於延遲載入的實現

// "這是 $app 物件的make方法,通過判斷是否存在延遲載入的成員,如存在且沒有在$instances中共享
的物件就會被載入,laravel 本身就是功能非常龐大的框架,因此延遲載入也是對效能提升的一種手段!"

public function make($abstract, array $parameters = [])
{
    $abstract = $this->getAlias($abstract);

    if (isset($this->deferredServices[$abstract]) && ! isset($this->instances[$abstract])) {
        $this->loadDeferredProvider($abstract);
    }

    return parent::make($abstract, $parameters);
}
複製程式碼

總結

分析到這裡基本上 Laravel 的服務提供者功能是如何運作的就分解完了

其中關於 FilesystemProviderRepository

物件的功能沒有詳細分解,這裡東西並不多,不展開了!

還有就是 PackageManifest::class這個物件的功能主要是從

composer.json 的互動以及構建 bootstrap/cache 下面的快取檔案。

可以梳理一下思路:

框架是怎麼載入的 (優先快取檔案,對比更新)

provider 提供了那些功能(延遲載入,事件監聽)...

包自動發現的實現! (通過讀取composer.json)

延遲載入的實現邏輯

相關文章