3.2 - Laravel - 5.6 - Route - RoutingServiceProvider註冊

HarveyNorman發表於2020-09-23

RoutingServiceProvider對路由來說是第一個需要載入的服務。提供了很多必要的繫結。

載入路徑步驟如下

  1. index.php 引入app.php

    require_once __DIR__.'/../bootstrap/app.php';
  2. bootstrap/app.php 檔案中會建立容器物件Application

    $app = new Illuminate\Foundation\Application(
     realpath(__DIR__.'/../')
    );
  3. 在namespace Illuminate\Foundation\Application 下的Apllication的建構函式中會呼叫registerBaseServiceProviders方法。

    public function __construct($basePath = null)
    {
     ...
     $this->registerBaseServiceProviders();
     ...
    }
  4. registerBaseServiceProviders方法如下,會呼叫register方法註冊RoutingServiceProvider方法。

呼叫register方法會去執行這個RoutingServiceProvider中的register方法中的邏輯。可以把需要的邏輯比如繫結一些必要的物件等等寫在這個register方法中。
(register方法的邏輯參考上一篇 application的register)

protected function registerBaseServiceProviders()
{
    $this->register(new EventServiceProvider($this));
    $this->register(new LogServiceProvider($this));
    $this->register(new RoutingServiceProvider($this));
}
  1. 主要看下 RoutingServiceProvider 的 register方法, 內容如下。
    public function register()
    {
     $this->registerRouter();
     $this->registerUrlGenerator();
     $this->registerRedirector();
     $this->registerPsrRequest();
     $this->registerPsrResponse();
     $this->registerResponseFactory();
     $this->registerControllerDispatcher();
    }

5.1 $this->registerRouter();
這個方法就是通過singleton方法註冊router例項到容器中。(router物件提供很多必要的方法幫助我們操作後面會生成的route物件)。程式碼如下。

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

很簡單,就是繫結一個閉包,閉包會建立router物件返回。

5.2 $this->registerUrlGenerator(); 這一步就是繫結url和其產生UrlGenerator物件的回撥方法。就是註冊一個url產生器。

protected function registerUrlGenerator()
{
    $this->app->singleton('url', function ($app) {
        $routes = $app['router']->getRoutes();
        $app->instance('routes', $routes);
        return new UrlGenerator($routes, $app->rebinding(
            'request', 
            $this->requestRebinder()), 
            $app['config']['app.asset_url']
        );
    });

    $this->app->extend('url', function (UrlGeneratorContract $url, $app) {
        $url->setSessionResolver(function () {
        return $this->app['session'] ?? null;});

        $url->setKeyResolver(function () {
            return $this->app->make('config')->get('app.key');
        });

        $app->rebinding('routes', function ($app, $routes) {
            $app['url']->setRoutes($routes);
        });
        return $url;
    });
}

這裡主要分兩步,分別使用singleton和extend方法繫結欄位名為url對應的閉包。

singleton會把url和其閉包繫結。但是不會執行閉包,要在make解析的時候才執行,執行完會存入instance陣列中。

extend繫結對前面這個url繫結的擴充套件。如果url已經解析過了就會把extend返回值替換instance中的值,如果沒有就會把方法存入extender陣列以備後用。簡單說就是extend是對singleton的url的擴充套件。

1.1 $routes = $app['router']->getRoutes();
獲取router物件的所有routes物件。(route物件就是我們產生的一個一個路由物件,用來和請求request匹配)
1.2 把route物件集合以名字routes存入instance陣列中。
1.2 返回一個UrlGenerator物件。可以生成url的物件。
1.2.1 $app->rebinding('request', $this->requestRebinder()
requestRebinder方法返回一個設定request到$app['url']的閉包。程式碼如下

protected function requestRebinder()
{
    return function ($app, $request) {
        $app['url']->setRequest($request);
    };
}

rebinding通常對已經解析過的abstruct,重新繫結的時候觸發回撥函式。
但是他還有個特性,如果當前abstruct以前被繫結過,他會馬上解析當前的abstruct。
(abstruct這裡就是’url’)。
這裡就是,如果request被繫結過就會馬上解析這個request物件。

整句的意思就是如果request被繫結過,就會馬上解析request返回,同時還把rebinding的回撥函式存入reboundCallbacks陣列中等待再次繫結的時候觸發。很巧妙。

1.2.2 $app['config']['app.asset_url'] 是asset靜態檔案路徑url

2.1 第二個extend繫結,返回一個方法,觸發的時候需要兩個引數:app和UrlGeneratorContract的實現類(就是上面的UrlGenerator物件)。

2.2 設定一些必要的特性以便以後使用

$url->setSessionResolver(function () {
    return $this->app['session'] ?? null;
});
$url->setKeyResolver(function () {
    return $this->app->make('config')->get('app.key');
});

這裡設定了一些回撥函式給UrlGenerator物件,到時候可以觸發獲取一些資訊,比如session以及配置檔案中的app.key的值。

2.3 和前面同理。rebinding會對routes馬上解析,因為上面已經使用instances繫結過routes了。返回routes解析後的物件。同時會把rebinding回撥函式存入以備後用。

$app->rebinding('routes', function ($app, $routes) {
    $app['url']->setRoutes($routes);
});

最後返回這個url物件。

前面提及,extend有替換的作用,前提是這個繫結已經被解析過。在這裡,singleton繫結url和extend繫結url之間沒有解析的過程,所以就沒有替換的情況,他會依次執行,先解析singleton然後觸發extend的回撥,相當於給url物件做了一個輔助擴充套件。

5.3 $this->registerRedirector(); 註冊一個重定向物件。

protected function registerRedirector()
{
    $this->app->singleton('redirect', function ($app) {
        $redirector = new Redirector($app['url']);
        if (isset($app['session.store'])) {
            $redirector->setSession($app['session.store']);
        }
        return $redirector;
    });
}

1.就是繫結redirect和其回撥函式。
2.判斷$app['session.store']是否存在,存在把其存入redirect物件中。

session.store 是Session驅動器,是Illuminate\Session\Store的例項,Store類實現了Illuminate\Contracts\Session\Session向開發者提供了統一的介面來訪問Session資料,驅動器通過不同的SessionHandler來訪問database、redis、memcache等不同的儲存介質裡的session資料。

5.4 registerPsrRequest和registerPsrResponse放一起。繫結滿足psr-7標準的request和response。但是不知道具體如何使用。程式碼中無跡可尋。請大牛指點。

protected function registerPsrRequest()
{
    $this->app->bind(ServerRequestInterface::class, function ($app) {
        return (new DiactorosFactory)->createRequest($app->make('request'));
    });
}

protected function registerPsrResponse()
{
    $this->app->bind(ResponseInterface::class, function () {
        return new PsrResponse;
    });
}

5.5 註冊響應的工廠物件,主要用來生成返回給使用者的資料。

protected function registerResponseFactory()
{
    $this->app->singleton(ResponseFactoryContract::class, function ($app) {
        return new ResponseFactory($app[ViewFactoryContract::class], $app['redirect']);
    });
}

5.6 這個是註冊控制器的分發物件。ControllerDispatcher這個物件主要用來觸發request對應的controller邏輯。

protected function registerControllerDispatcher()
{
    $this->app->singleton(ControllerDispatcherContract::class, function ($app) {
        return new ControllerDispatcher($app);
    });
}

5.5 5.6沒有詳細解釋因為不在本章範圍,後面遇到操作再補充。

這就是整個RoutingServiceProvider載入過程。

總結:
1.RoutingServiceProvider的載入不復雜,主要是要明確他是在容器初始化的時候載入的,比其他一些serviceprovider要早。
2.連續使用singleton和extend 可以避免extend繫結的替換特性。
3.RoutingServiceProvider的載入的作用主要還是繫結各個回撥函式,還並沒有真正觸發這些回撥。在給後面的操作打下基礎。

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

相關文章