3.2 - Laravel - 5.6 - Route - Application的Register方法原始碼分析

HarveyNorman發表於2020-09-21

Application 提供了一個register方法用來註冊service provider。舉例使用如下:


protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}

這個方法是Application初始化時註冊Serviceprovider的方法。
最後一個ServiceProvider涉及到了RoutingServiceProvider的註冊。這個RoutingServiceProvider對路由機制來說很重要,所以在分析它之前,先具體來看看Application的序號產生器制。方便後面閱讀。

  1. 檢視application中的register方法原始碼
public function register($provider, $force = false)
{
    if (($registered = $this->getProvider($provider)) && ! $force) {
        return $registered;
    }

    if (is_string($provider)) {
        $provider = $this->resolveProvider($provider);
    }

    $provider->register();

    if (property_exists($provider, 'bindings')) {
        foreach ($provider->bindings as $key => $value) {
        $this->bind($key, $value);
        }
    }

    if (property_exists($provider, 'singletons')) {
        foreach ($provider->singletons as $key => $value) {
        $this->singleton($key, $value);
        }
    }

    $this->markAsRegistered($provider);

    if ($this->isBooted()) {
        $this->bootProvider($provider);
    }

    return $provider;
}

1.1 第一步

if (($registered = $this->getProvider($provider)) && ! $force) {
    return $registered;
}

1.1.1 主要就是呼叫getProvider($provider)。作用就是檢查前面是否已經載入了我們需要的provider,如果是,返回這個provider,不用再載入。如果不是,就返回null。

array_values: 返回陣列的value值不包含key

看下getProvider()方法原始碼

public function getProvider($provider)
{
    return array_values($this->getProviders($provider))[0] ?? null;
}

$this->getProviders($provider))[0]方法獲取的陣列的第一個值,說明返回的是一個陣列。

這個方法沒什麼好說的就是呼叫方法getProviders()

注意最後有個s和上面的getProvider()的區別。

1.1.1.1 我們看下getProviders()方法:

public function getProviders($provider)
{
    $name = is_string($provider) ? $provider : get_class($provider);
        return Arr::where($this->serviceProviders, function ($value) use ($name) {
            return $value instanceof $name;
    });
}

這個方法分兩步,

a.如果引數$provider是字串,直接賦值給變數$name,如果不是,使用get_class獲取該引數$provider所屬類的路徑,然後返回。
在我們當前的例子中,$providerRoutingServiceProvider($this)物件,所以返回的是類RoutingServiceProvider的類路徑。

這裡我們知道 provider可以直接是一個類路徑的字串,聯想到serviceprovider註冊的另外一種形式是通過配置類路徑來實現就很好理解了。

b.然後呼叫了工具類Arr的where方法。

public static function where($array, callable $callback)
{
    return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
}

where方法的功能是使用第二個引數(回撥函式)對第一個引數(陣列)進行過濾查詢,找出需要的值。

在這裡:
I.第一個引數$array$this->serviceProviders陣列
II.$this->serviceProviders是前面所有已經註冊了的serviceprovider

所以就是在$this->serviceProviders這個存放前面已經註冊的service provider中找到對應的provider返回。

總結:1.1

現在我們知道了$this->getProvider($provider)) 得到的是在serviceprovider陣列中獲取已經存在的provider。
那這句就是判斷如果已經註冊了需要的provider,同時變數force是false的情況下(這個情況下force用來控制是否需要查詢存在的serviceprovider),直接返回存在的$provider。



1.2 如果這個provider是一個string,我們使用`resolveProvider`方法來處理

if (is_string($provider)) {
    $provider = $this->resolveProvider($provider);
}

1.2.1 看下resolveProvider方法,很簡單直接new一個provider物件返回。

public function resolveProvider($provider)
{
    return new $provider($this);
}


1.3 第三步程式碼:

$provider->register();

這裡$provider通過第二步,已經肯定是一個provider物件了。就直接呼叫該物件的register方法。

當前例子中就是RoutingServiceProvider($this)物件的register方法。

1.3.1 因為RoutingServiceProvider的實現邏輯不是本文的目的,所以簡單看一下。

進入RoutingServiceProvider類的register方法。

方法如下,提供了幾個方法。


public function register()
{
    $this->registerRouter();
    $this->registerUrlGenerator();
    $this->registerRedirector();
    $this->registerPsrRequest();
    $this->registerPsrResponse();
    $this->registerResponseFactory();
    $this->registerControllerDispatcher();
}

我們先只看其中第一個registerRouter()方法為例.

protected function registerRouter()
{
    $this->app->singleton('router', function ($app) {
        return new Router($app['events'], $app);
    });
}

使用singleton單例繫結router,對應的回撥函式會返回一個Router物件。
同時在呼叫回撥函式的時候需要外部依賴$app容器作為引數。

這樣就得到了路由器router物件用於後面操控route路由物件了。


總結1.3
現在看來register方法就是每個provider的基礎方法,基本包含了所有的provider基本邏輯程式。


1.4 呼叫provider物件的register方法完成以後,如果provider中存在兩個屬性 `bindings`和`singletons`, 就會在這個時候把這兩個屬性(陣列集合)中的每個欄位值都繫結到容器中。

其實看名字就能明白。bindings就是表示普通繫結。singletons就是單例繫結。

簡單說就是,provider類中你可以配置兩個陣列, 分別叫bindingssingletons,在laravel執行完provider的register方法後就可以被laravel依次繫結到容器中。便於後面的使用。


if (property_exists($provider, 'bindings')) {
    foreach ($provider->bindings as $key => $value) {
        $this->bind($key, $value);
    }
}

if (property_exists($provider, 'singletons')) {
    foreach ($provider->singletons as $key => $value) {
        $this->singleton($key, $value);
    }
}


1.5 第五步

$this->markAsRegistered($provider);

到這裡要把這個已經註冊完成的provider物件進行儲存,就是存入serviceProviders陣列中,後面再有需求的時候不需要再次進行註冊。對應了第一步的作用。避免重複載入。

1.5.1 我們看下原始碼就知道了,做了兩件事。

a.儲存這個註冊的provider到serviceProviders陣列中。

b.把對應的類名儲存到loadedProviders陣列中,以備後用。

protected function markAsRegistered($provider)
{
    $this->serviceProviders[] = $provider;
    $this->loadedProviders[get_class($provider)] = true;
}


1.6 第六步

if ($this->isBooted()) {
    $this->bootProvider($provider);
}

首先判斷當前的application這個容器物件是否已經啟動過了,什麼意思呢,簡單說,就是application的boot方法是否已經執行過了。

每個provider都提供了一個boot方法用來提供一些額外的邏輯在完成provider註冊後執行。通常這些boot方法會在Application的boot方法中會統一執行。
程式碼如下:

public function boot()
{
    ...
    array_walk($this->serviceProviders, function ($p) {
        $this->bootProvider($p);
    });
    ...
}

boot方法的執行相對靠後,(在application初始化後)他是通過類BootProviders的bootstrap方法觸發的。BootProviders類又會被當做一個serviceprovider在application初始化後期和其他serviceprovider一起載入然後觸發。先這樣簡單描述,不在本章討論範圍。
\Illuminate\Foundation\Bootstrap\BootProviders::class,

我們可以看到 在laravel中,serviceprovider的註冊一部分發生在application初始化後。一部分發生在application初始化時,當前的這個RoutingServiceProvider就是在初始化時。(具體參考後面RoutingServiceProvider原始碼分析)

但是有些provider是在application初始化後註冊的,如果註冊的時機晚於BootProviders的註冊,這個時候application不會再執行它的boot方法了,所以需要在這一步中主動去觸發provider的boot。

1.6.1 provider中是否存在boot方法,如果存在,觸發boot方法中的邏輯。

protected function bootProvider(ServiceProvider $provider)
{
    if (method_exists($provider, 'boot')) {
        return $this->call([$provider, 'boot']);
    }
}

1.7 最後返回註冊好後的provider。
整個註冊完成。

總結:

1.註冊一個provider之前,先去檢視是不是已經註冊這個服務了,避免反覆註冊浪費資源

2.註冊一個provider 簡單來說就是執行provider中的register方法的邏輯。

3.註冊完成後會把當前這個provider物件存入指定的陣列中儲存以便後面不再反覆註冊。

4.provider通常提供bindingssingletons兩個陣列變數,用來提供一些provider需要的依賴繫結到容器中。在註冊完成之後繫結。

4.註冊完成後,繫結完成後,額外的邏輯可以在provider的boot方法中實現。當然沒有額外邏輯。可以不寫boot

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章