iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 20
0
Software Development

Laravel 原始碼分析系列 第 20

解析 Middleware 的實作細節

Middleware 是由 Pipeline 實作的,而程式裡出現 Pipeline 有兩次。一次是 Global middleware,也就是 Http Kernel 所寫的這段程式碼:

return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());

另一個則是 Router 裡面的這段程式碼:

$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);

return (new Pipeline($this->container))
                ->send($request)
                ->through($middleware)
                ->then(function ($request) use ($route) {
                    return $this->prepareResponse(
                        $request, $route->run()
                    );
                });

今天繼續看 gatherRouteMiddleware()

public function gatherRouteMiddleware(Route $route)
{
    // 呼叫 `gatherMiddleware()` 取得一組 middleware 後,再解析它
    $middleware = collect($route->gatherMiddleware())->map(function ($name) {
        return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
    })->flatten();

    return $this->sortMiddleware($middleware);
}

gatherMiddleware() 可以取得屬於某個 Route 的 middleware:

public function gatherMiddleware()
{
    // computedMiddleware 使用類似單例的方法產生
    if (! is_null($this->computedMiddleware)) {
        return $this->computedMiddleware;
    }

    $this->computedMiddleware = [];

    return $this->computedMiddleware = array_unique(array_merge(
        // middleware 由兩個地方產生的
        $this->middleware(), $this->controllerMiddleware()
    ), SORT_REGULAR);
}

public function middleware($middleware = null)
{
    // 取得 $action['middleware']
    if (is_null($middleware)) {
        return (array) ($this->action['middleware'] ?? []);
    }

    // 這邊以下就是在設定 Route 的 middleware
    if (is_string($middleware)) {
        $middleware = func_get_args();
    }

    $this->action['middleware'] = array_merge(
        (array) ($this->action['middleware'] ?? []), $middleware
    );

    return $this;
}

public function controllerMiddleware()
{
    // 如果不是 controller action 就不需要解析
    if (! $this->isControllerAction()) {
        return [];
    }

    // 從 controller dispatcher 取得 middleware
    return $this->controllerDispatcher()->getMiddleware(
        $this->getController(), $this->getControllerMethod()
    );
}

$this->controllerDispatcher()->getMiddleware() 的程式碼如下:

public function getMiddleware($controller, $method)
{
    // 如果 Controller 沒有 getMiddleware() 就回傳空 array
    if (! method_exists($controller, 'getMiddleware')) {
        return [];
    }

    // 解析 Controller::getMiddleware() 的內容
    return collect($controller->getMiddleware())->reject(function ($data) use ($method) {
        return static::methodExcludedByOptions($method, $data['options']);
    })->pluck('middleware')->all();
}

所以 Controller 也有辦法定義屬於它的 middleware。文件裡面並沒有明確地提到,只有簡單帶過。

使用範例是像下面這樣:

class IndexController extends Controller
{
    public function __construct()
    {
        $this->middleware(FooMiddleware::class, [
            'only' => ['hello', 'world']
        ]);
        $this->middleware(BarMiddleware::class, [
            'except' => ['welcome']
        ]);
    }
}

onlyexcept 的語意就不多解釋,後面接的字串代表的是方法名稱,如 except 的例子會對應到的方法指的是 IndexController@welcome

當把兩組 middleware 依序都拿到之後,使用 MiddlewareNameResolver::resolve 解析,傳入的第二個與第三個參數分別是 middlewaremiddlewareGroups,這兩個參數是何時設定的呢?其實在一開始分析 Bootstrap 時有帶過,它是 Http Kernel 初始化會做的事之一。

public static function resolve($name, $map, $middlewareGroups)
{
    // 如果傳入的 $name 是 Closure 的話,就直接回傳來用
    if ($name instanceof Closure) {
        return $name;
    }

    // 如果 map 裡找得到,而且也是 Closure 的話,就直接回傳來用
    if (isset($map[$name]) && $map[$name] instanceof Closure) {
        return $map[$name];
    }

    // 如果 group 裡有的話,就解析 group 裡面的 middleware
    if (isset($middlewareGroups[$name])) {
        return static::parseMiddlewareGroup($name, $map, $middlewareGroups);
    }

    list($name, $parameters) = array_pad(explode(':', $name, 2), 2, null);

    return ($map[$name] ?? $name).(! is_null($parameters) ? ':'.$parameters : '');
}

最後會回傳的形式,以 API 的 throttle:60,1 設定來說,最後的結果會長的像下面這個樣子:

Illuminate\Routing\Middleware\ThrottleRequests:60,1

這會在 Pipeline 的時候,再解析出來成為類別名與參數。

最後會使用 sortMiddleware() 排序:

protected function sortMiddleware(Collection $middlewares)
{
    return (new SortedMiddleware($this->middlewarePriority, $middlewares))->all();
}

排序會受 middlewarePriority 參數的影響。而從整體順序來看,我們可以了解 Middleware 的優先權如下:

Global > Route > Controller

其實分析程式,會提高對掌握框架的程度,並且可以正確地用它,是非常好的。


上一篇
分析 Marcoable
下一篇
分析 Log
系列文
Laravel 原始碼分析46

尚未有邦友留言

立即登入留言