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

開始

咱們直接從 Kernelhandle() 方法中開始分析,handle() 負責處理了請求,全部框架的啓動也是在這裏開始!php

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());
}
複製代碼

下面是內核引導啓動函數laravel

$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() 方法,繼續下去json

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) 方法bootstrap

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

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

繼續看 ApplicationregisterConfiguredProviders()bash

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 框架默認的加載的服務提供者app

'providers' => [

    Illuminate\Auth\AuthServiceProvider::class,
   
    ... 省略相似代碼
   
    Illuminate\View\ViewServiceProvider::class,
    
    
    
    App\Providers\AppServiceProvider::class,
    
    ... 省略相似代碼
    
    App\Providers\RouteServiceProvider::class,

],
複製代碼

經過集合來操做,按照命名空間來分區,這裏目前只有兩組 IlluminateApp 開頭的類composer

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

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

主要實現了從全部的包的 composer.json 文件下讀取 laravel 下的 providers 中的內容。

在引入這個功能後不少包都不須要咱們手動再去 app.providers 中去配置了。

在這行代碼執行完成以後 $providers 會變成一個分紅了三段的集合對象。

繼續看下去 (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 的樣子

繼續看下一段,若是傳入的 $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)

延遲加載的實現邏輯

相關文章
相關標籤/搜索