Laravel 請求生命週期

Epona發表於2019-05-09

本文主要介紹了一個 Http 請求在 Laravel 中是怎樣處理的。

public/index.php

所有 Laravel 程式均起始於 public/index.php 檔案。

<?php

define('LARAVEL_START', microtime(true));

require __DIR__.'/../vendor/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

$response->send();

$kernel->terminate($request, $response);

index 裡的程式碼很簡單,首先定義了一個常量 LARAVEL_START ,接著引入了 composer 庫中的 autoloader。隨後載入 Laravel 的 Application 例項。接著到了最重要的部分,通過 Http Kernel 處理請求,最後將響應進行返回。

處理請求

在這裡我們具體關注 Http Kernel 是怎樣處理請求的。在 Illuminate\Foundation\Http\Kernel 中我們看到有 handle 方法。具體如下:

public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();

        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        $this->reportException($e);

        $response = $this->renderException($request, $e);
    } catch (Throwable $e) {
        $this->reportException($e = new FatalThrowableError($e));

        $response = $this->renderException($request, $e);
    }

    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );

    return $response;
}

在上面的程式碼中,首先我們對 Http 方法確保能夠進行覆蓋處理,具體的用途參考這裡,接著 Laravel 將請求通過sendRequestThroughRouter 進行處理。


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());
}

在這裡我們例項化 \Illuminate\Http\Request,然後引導相關的載入程式類,具體如下:

protected $bootstrappers = [
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, // 載入env環境變數
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class, // 載入配置檔案
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class, // 異常處理
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class, // 註冊 Facades
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 註冊 Providers
    \Illuminate\Foundation\Bootstrap\BootProviders::class, // 啟動 Providers
];

public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}

protected function bootstrappers()
{
    return $this->bootstrappers;
}

引導完畢之後,通過管道(pipeline)進行請求處理。關於管道的原理會在後面的文章中進行分析,這裡你可以理解為迴圈使用中介軟體來處理請求。當所有的中介軟體處理完之後,交由dispatchToRouter進行下一步處理。

匹配路由

// Kernel.php
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}

// Illuminate\Routing\Router
public function dispatch(Request $request)
{
    $this->currentRequest = $request;

    return $this->dispatchToRoute($request);
}

public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

protected function findRoute($request)
{
    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route);

    return $route;
}

protected function runRoute(Request $request, Route $route)
{
    $request->setRouteResolver(function () use ($route) {
        return $route;
    });

    $this->events->dispatch(new Events\RouteMatched($route, $request));

    return $this->prepareResponse($request,
        $this->runRouteWithinStack($route, $request)
    );
}

protected function runRouteWithinStack(Route $route, Request $request)
{
    $shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
                            $this->container->make('middleware.disable') === true;

    $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()
                        );
                    });
}

public function prepareResponse($request, $response)
{
    return static::toResponse($request, $response);
}

public static function toResponse($request, $response)
{
    if ($response instanceof Responsable) {
        $response = $response->toResponse($request);
    }

    if ($response instanceof PsrResponseInterface) {
        $response = (new HttpFoundationFactory)->createResponse($response);
    } elseif (! $response instanceof SymfonyResponse &&
               ($response instanceof Arrayable ||
                $response instanceof Jsonable ||
                $response instanceof ArrayObject ||
                $response instanceof JsonSerializable ||
                is_array($response))) {
        $response = new JsonResponse($response);
    } elseif (! $response instanceof SymfonyResponse) {
        $response = new Response($response);
    }

    if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
        $response->setNotModified();
    }

    return $response->prepare($request);
}

// Illuminate\Routing\Route

public function run()
{
    $this->container = $this->container ?: new Container;

    try {
        if ($this->isControllerAction()) {
            return $this->runController();
        }

        return $this->runCallable();
    } catch (HttpResponseException $e) {
        return $e->getResponse();
    }
}

在上面的程式碼中,我們首先要找到匹配的路由,即findRoute方法。其中 match 方法的大致邏輯為:

  1. 獲取到當前程式中的所有路由地址
  2. 對比 uri,http 方法,scheme 和 host
  3. 返回符合條件的第一個路由
  4. 如果未找到丟擲異常

如果找到了匹配的路由,觸發路由匹配事件,隨後在 runRouteWithinStack 中,我們繼續使用管道處理。接著在 $route->run() 中返回結果(根據是控制器方法還是回撥方法分別進行處理)。最後根據不同的響應型別和狀態設定不同的 Header 和 請求結果。

至此 Laravel 的請求生命週期基本結束。以後的文章會繼續研究 Laravel 的其他功能原理。

There's nothing wrong with having a little fun.

相關文章