Laravel 啓動流程

Last-Modified: 2019年5月10日16:19:07

階段劃分

Laravel 5.5
請求到響應的整個執行階段歸納爲 4 個:
  1. 程序啓動準備階段

    • 文件自動加載
    • 服務容器實例化
    • 基礎服務提供者的註冊
    • 核心類的實例化
  2. 請求實例化階段

    • 實例化 Request 實例
  3. 請求處理階段

    • 準備請求處理的環境
    • 將請求實例通過中間件處理 及 通過路由和控制器的分發控制
  4. 響應發送和程序終止階段

    • 將響應內容返回給客戶端
    • 記錄與客戶端有關的信息等

1. 程序啓動準備

程序入口在 index.php

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

創建服務容器實例

服務容器的創建在 bootstrap\app.php 中進行.

$app = new Illuminate\Foundation\Application(
    realpath(__DIR__.'/../')
);

1.1 容器基礎配置

容器 Application 的構造函數:

public function __construct($basePath = null)
{
    if ($basePath) {
        $this->setBasePath($basePath);
    }

    $this->registerBaseBindings();

    $this->registerBaseServiceProviders();

    $this->registerCoreContainerAliases();
}

構造函數 主要完成以下基本配置:

  • 目錄路徑(綁定到容器中, 並提供類方法獲取子目錄)

    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');
    
        $this->bindPathsInContainer();
    
        return $this;
    }
    
    protected function bindPathsInContainer()
        {
            $this->instance('path', $this->path());
            $this->instance('path.base', $this->basePath());
            $this->instance('path.lang', $this->langPath());
            $this->instance('path.config', $this->configPath());
            $this->instance('path.public', $this->publicPath());
            $this->instance('path.storage', $this->storagePath());
            $this->instance('path.database', $this->databasePath());
            $this->instance('path.resources', $this->resourcePath());
            $this->instance('path.bootstrap', $this->bootstrapPath());
        }
  • 綁定容器自身

    protected function registerBaseBindings()
    {
        static::setInstance($this);
    
        $this->instance('app', $this);
    
        $this->instance(Container::class, $this);
    
        $this->instance(PackageManifest::class, new PackageManifest(
            new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
        ));
    }
  • 基礎服務註冊( Event, Log, Route)

    protected function registerBaseServiceProviders()
    {
        $this->register(new EventServiceProvider($this));
    
        $this->register(new LogServiceProvider($this));
    
        $this->register(new RoutingServiceProvider($this));
    }
  • 別名註冊

    多個接口名 對應一個簡短別名, 後續在註冊服務時只需綁定到別名上即可 (而不必綁定到具體接口名)

    public function registerCoreContainerAliases()
    {
        foreach ([
            'app'                  => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class,  \Psr\Container\ContainerInterface::class],
            'auth'                 => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
            'auth.driver'          => [\Illuminate\Contracts\Auth\Guard::class],
            'blade.compiler'       => [\Illuminate\View\Compilers\BladeCompiler::class],
            'cache'                => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
            'cache.store'          => [\Illuminate\Cache\Repository::class, \Illuminate\Contracts\Cache\Repository::class],
            'config'               => [\Illuminate\Config\Repository::class, \Illuminate\Contracts\Config\Repository::class],
            'cookie'               => [\Illuminate\Cookie\CookieJar::class, \Illuminate\Contracts\Cookie\Factory::class, \Illuminate\Contracts\Cookie\QueueingFactory::class],
            'encrypter'            => [\Illuminate\Encryption\Encrypter::class, \Illuminate\Contracts\Encryption\Encrypter::class],
            'db'                   => [\Illuminate\Database\DatabaseManager::class],
            'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
            'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
            'files'                => [\Illuminate\Filesystem\Filesystem::class],
            'filesystem'           => [\Illuminate\Filesystem\FilesystemManager::class, \Illuminate\Contracts\Filesystem\Factory::class],
            'filesystem.disk'      => [\Illuminate\Contracts\Filesystem\Filesystem::class],
            'filesystem.cloud'     => [\Illuminate\Contracts\Filesystem\Cloud::class],
            'hash'                 => [\Illuminate\Contracts\Hashing\Hasher::class],
            'translator'           => [\Illuminate\Translation\Translator::class, \Illuminate\Contracts\Translation\Translator::class],
            'log'                  => [\Illuminate\Log\Writer::class, \Illuminate\Contracts\Logging\Log::class, \Psr\Log\LoggerInterface::class],
            'mailer'               => [\Illuminate\Mail\Mailer::class, \Illuminate\Contracts\Mail\Mailer::class, \Illuminate\Contracts\Mail\MailQueue::class],
            'auth.password'        => [\Illuminate\Auth\Passwords\PasswordBrokerManager::class, \Illuminate\Contracts\Auth\PasswordBrokerFactory::class],
            'auth.password.broker' => [\Illuminate\Auth\Passwords\PasswordBroker::class, \Illuminate\Contracts\Auth\PasswordBroker::class],
            'queue'                => [\Illuminate\Queue\QueueManager::class, \Illuminate\Contracts\Queue\Factory::class, \Illuminate\Contracts\Queue\Monitor::class],
            'queue.connection'     => [\Illuminate\Contracts\Queue\Queue::class],
            'queue.failer'         => [\Illuminate\Queue\Failed\FailedJobProviderInterface::class],
            'redirect'             => [\Illuminate\Routing\Redirector::class],
            'redis'                => [\Illuminate\Redis\RedisManager::class, \Illuminate\Contracts\Redis\Factory::class],
            'request'              => [\Illuminate\Http\Request::class, \Symfony\Component\HttpFoundation\Request::class],
            'router'               => [\Illuminate\Routing\Router::class, \Illuminate\Contracts\Routing\Registrar::class, \Illuminate\Contracts\Routing\BindingRegistrar::class],
            'session'              => [\Illuminate\Session\SessionManager::class],
            'session.store'        => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
            'url'                  => [\Illuminate\Routing\UrlGenerator::class, \Illuminate\Contracts\Routing\UrlGenerator::class],
            'validator'            => [\Illuminate\Validation\Factory::class, \Illuminate\Contracts\Validation\Factory::class],
            'view'                 => [\Illuminate\View\Factory::class, \Illuminate\Contracts\View\Factory::class],
        ] as $key => $aliases) {
            foreach ($aliases as $alias) {
                $this->alias($key, $alias);
            }
        }
    }

1.2 核心類綁定

$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

綁定重要接口:

  • Http 核心類
  • 命令行 核心類
  • 異常處理類

1.3 實例化 Http 核心類

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

Http 核心類的構造函數

public function __construct(Application $app, Router $router)
{
    $this->app = $app;
    $this->router = $router;

    $router->middlewarePriority = $this->middlewarePriority;

    foreach ($this->middlewareGroups as $key => $middleware) {
        $router->middlewareGroup($key, $middleware);
    }

    foreach ($this->routeMiddleware as $key => $middleware) {
        $router->aliasMiddleware($key, $middleware);
    }
}

上述過程主要做的事是將中間件賦值給路由

  • 中間件順序優先級列表
  • 中間件組
  • 中間件別名

核心類 app/Http/Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // 全局中間件,最先調用
    protected $middleware = [

        // 檢測是否應用是否進入『維護模式』
        // 見:https://d.laravel-china.org/docs/5.5/configuration#maintenance-mode
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,

        // 檢測請求的數據是否過大
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,

        // 對提交的請求參數進行 PHP 函數 `trim()` 處理
        \App\Http\Middleware\TrimStrings::class,

        // 將提交請求參數中空子串轉換爲 null
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,

        // 修正代理服務器後的服務器參數
        \App\Http\Middleware\TrustProxies::class,
    ];

    // 定義中間件組
    protected $middlewareGroups = [

        // Web 中間件組,應用於 routes/web.php 路由文件
        'web' => [
            // Cookie 加密解密
            \App\Http\Middleware\EncryptCookies::class,

            // 將 Cookie 添加到響應中
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,

            // 開啓會話
            \Illuminate\Session\Middleware\StartSession::class,

            // 認證用戶,此中間件以後 Auth 類才能生效
            // 見:https://d.laravel-china.org/docs/5.5/authentication
            \Illuminate\Session\Middleware\AuthenticateSession::class,

            // 將系統的錯誤數據注入到視圖變量 $errors 中
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,

            // 檢驗 CSRF ,防止跨站請求僞造的安全威脅
            // 見:https://d.laravel-china.org/docs/5.5/csrf
            \App\Http\Middleware\VerifyCsrfToken::class,

            // 處理路由綁定
            // 見:https://d.laravel-china.org/docs/5.5/routing#route-model-binding
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        // API 中間件組,應用於 routes/api.php 路由文件
        'api' => [
            // 使用別名來調用中間件
            // 請見:https://d.laravel-china.org/docs/5.5/middleware#爲路由分配中間件
            'throttle:60,1',
            'bindings',
        ],
    ];

    // 中間件別名設置,允許你使用別名調用中間件,例如上面的 api 中間件組調用
    protected $routeMiddleware = [

        // 只有登錄用戶才能訪問,我們在控制器的構造方法中大量使用
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,

        // HTTP Basic Auth 認證
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,

        // 處理路由綁定
        // 見:https://d.laravel-china.org/docs/5.5/routing#route-model-binding
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,

        // 用戶授權功能
        'can' => \Illuminate\Auth\Middleware\Authorize::class,

        // 只有遊客才能訪問,在 register 和 login 請求中使用,只有未登錄用戶才能訪問這些頁面
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,

        // 訪問節流,類似於 『1 分鐘只能請求 10 次』的需求,一般在 API 中使用
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    ];
}

2. 請求實例化

以處理 Http 請求爲例

index.php 入口文件

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

請求是通過 Illuminate\Http\Request::capture() 實例化的, 主要是將請求信息以對象形式表現出來

3. 請求處理

入口文件:

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

$kernel->handle(...) 處理請求過程

Illuminate\Foundation\Http\Kernel
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;
}


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


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

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

實際處理請求邏輯主要在 sendRequestThroughRouter 方法中, 它主要做了:

  • 核心類的初始化
  • 經由中間件過濾後將請求最終交由 Router 處理

    對於 Http 請求處理, 中間件包括:

    protected $middleware = [
    
        \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    
        \App\Http\Middleware\TrimStrings::class,
    
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    
        \App\Http\Middleware\TrustProxies::class,
    
    ];
    

    該中間件數組定義在 Http 核心類中, 同時在覈心類的構造函數中傳遞給 Router

3.1 請求處理環境初始化

核心類的初始化 bootstrap()

protected $bootstrappers = [
    \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
    \Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
    \Illuminate\Foundation\Bootstrap\HandleExceptions::class,
    \Illuminate\Foundation\Bootstrap\RegisterFacades::class,
    \Illuminate\Foundation\Bootstrap\RegisterProviders::class,
    \Illuminate\Foundation\Bootstrap\BootProviders::class,
];

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

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

在服務容器 Application 類中

public function bootstrapWith(array $bootstrappers)
{
    $this->hasBeenBootstrapped = true;

    foreach ($bootstrappers as $bootstrapper) {
        $this['events']->fire('bootstrapping: '.$bootstrapper, [$this]);

        $this->make($bootstrapper)->bootstrap($this);

        $this['events']->fire('bootstrapped: '.$bootstrapper, [$this]);
    }
}

該步驟主要是主要是對核心類中定義的 $bootstrappers 數組元素(引導類)初始化.

bootstrap 過程具體是在服務容器來中進行, 由核心類調用並傳入待初始化的類

Http 核心類默認包含以下 6 個啓動服務:

1. 環境監測 \Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class

.env 文件中解析環境變量到 getevn(), $_ENV, $_SERVER

依賴 vlucas/phpdotenv 擴展包

2. 配置加載 \Illuminate\Foundation\Bootstrap\LoadConfiguration::class

載入 config 目錄下所有 php 配置文件, 並將生成的配置存儲類綁定到服務容器 $app['config']

同時配置時區及 多字節格式(utf8)

3. 常處理 \Illuminate\Foundation\Bootstrap\HandleExceptions::class

報告所有錯誤 error_report(E_ALL)

提供對未捕獲的異常, 錯誤的全局處理 set_error_handler, set_exception_handler, register_shutdown_function

4. 外觀註冊 \Illuminate\Foundation\Bootstrap\RegisterFacades::class

app.aliases 中讀取外觀配置數組

'aliases' => [

        'App' => Illuminate\Support\Facades\App::class,
        'Artisan' => Illuminate\Support\Facades\Artisan::class,
        'Auth' => Illuminate\Support\Facades\Auth::class,
        'Blade' => Illuminate\Support\Facades\Blade::class,
        'Broadcast' => Illuminate\Support\Facades\Broadcast::class,
        'Bus' => Illuminate\Support\Facades\Bus::class,
        'Cache' => Illuminate\Support\Facades\Cache::class,
        'Config' => Illuminate\Support\Facades\Config::class,
        'Cookie' => Illuminate\Support\Facades\Cookie::class,
        'Crypt' => Illuminate\Support\Facades\Crypt::class,
        'DB' => Illuminate\Support\Facades\DB::class,
        'Eloquent' => Illuminate\Database\Eloquent\Model::class,
        'Event' => Illuminate\Support\Facades\Event::class,
        'File' => Illuminate\Support\Facades\File::class,
        'Gate' => Illuminate\Support\Facades\Gate::class,
        'Hash' => Illuminate\Support\Facades\Hash::class,
        'Lang' => Illuminate\Support\Facades\Lang::class,
        'Log' => Illuminate\Support\Facades\Log::class,
        'Mail' => Illuminate\Support\Facades\Mail::class,
        'Notification' => Illuminate\Support\Facades\Notification::class,
        'Password' => Illuminate\Support\Facades\Password::class,
        'Queue' => Illuminate\Support\Facades\Queue::class,
        'Redirect' => Illuminate\Support\Facades\Redirect::class,
        'Redis' => Illuminate\Support\Facades\Redis::class,
        'Request' => Illuminate\Support\Facades\Request::class,
        'Response' => Illuminate\Support\Facades\Response::class,
        'Route' => Illuminate\Support\Facades\Route::class,
        'Schema' => Illuminate\Support\Facades\Schema::class,
        'Session' => Illuminate\Support\Facades\Session::class,
        'Storage' => Illuminate\Support\Facades\Storage::class,
        'URL' => Illuminate\Support\Facades\URL::class,
        'Validator' => Illuminate\Support\Facades\Validator::class,
        'View' => Illuminate\Support\Facades\View::class,

    ],

使用 spl_autoload_register(...) 處理類加載, 配合 class_alias() 提供類的別名調用

Facade 外觀類基類依賴 __callStatic` 調用方法( 使用服務容器實例化對應類)

5. 服務提供者註冊 \Illuminate\Foundation\Bootstrap\RegisterProviders::class

app.providers 中讀取所有服務提供者

'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        Illuminate\Bus\BusServiceProvider::class,
        Illuminate\Cache\CacheServiceProvider::class,
        Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class,
        Illuminate\Cookie\CookieServiceProvider::class,
        Illuminate\Database\DatabaseServiceProvider::class,
        Illuminate\Encryption\EncryptionServiceProvider::class,
        Illuminate\Filesystem\FilesystemServiceProvider::class,
        Illuminate\Foundation\Providers\FoundationServiceProvider::class,
        Illuminate\Hashing\HashServiceProvider::class,
        Illuminate\Mail\MailServiceProvider::class,
        Illuminate\Notifications\NotificationServiceProvider::class,
        Illuminate\Pagination\PaginationServiceProvider::class,
        Illuminate\Pipeline\PipelineServiceProvider::class,
        Illuminate\Queue\QueueServiceProvider::class,
        Illuminate\Redis\RedisServiceProvider::class,
        Illuminate\Auth\Passwords\PasswordResetServiceProvider::class,
        Illuminate\Session\SessionServiceProvider::class,
        Illuminate\Translation\TranslationServiceProvider::class,
        Illuminate\Validation\ValidationServiceProvider::class,
        Illuminate\View\ViewServiceProvider::class,

        /*
         * Package Service Providers...
         */

        /*
         * Application Service Providers...
         */
        App\Providers\AppServiceProvider::class,
        App\Providers\AuthServiceProvider::class,
        // App\Providers\BroadcastServiceProvider::class,
        App\Providers\EventServiceProvider::class,
        App\Providers\RouteServiceProvider::class,    # 路由表生成
    ],

服務提供者經過解析後分爲 3 種類型的服務提供者:

  • eager 類型

    馬上調用 register 註冊

  • deferred 類型

    記錄下來, 當服務容器解析對應服務時, 才註冊對應的服務提供者

  • when 類型

    記錄下來, 當對應 event 觸發時在註冊對應服務提供者

6. 啓動提供者 \Illuminate\Foundation\Bootstrap\BootProviders::class

調用服務容器的 boot() 方法, 依次調用在服務容器中 register 的所有服務提供者的 boot() 方法

3.2 路由處理請求

在內核處理請求, 將請求實例通過中間件處理後, 將請求的處理交給路由 Router 進行控制器的分發.

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

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

路由表存儲結構說明

Illuminate\Routing\Route 存儲單條路由

Illuminate\Routing\RouteCollection 保存所有 Route 實例, 形成路由表

Illuminate\Routing\Router 類實例持有 RouteCollection 路由表實例.

即, 一個 Router 持有一個 RouteCollection, 而 RouteCollection 擁有 N 個 Route

Router 中對請求的處理同樣經過一系列的 路由中間件

# 路由處理請求的入庫
public function dispatchToRoute(Request $request)
{
    return $this->runRoute($request, $this->findRoute($request));
}

# 根據請求的 url 和 method 查找對應的 route
protected function findRoute($request)
{
    $this->current = $route = $this->routes->match($request);

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

    return $route;
}

# 根據對應的請求和路由條目, 返回相應的 $response
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)
                                 );
}

# 請求經過路由中間件過濾後, 交由 route 的 run() 方法處理
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()
            );
        });
}

routerun() 方法最終將請求轉給 Illuminate\Routing\ControllerDispatcher::dispatch 處理

public function dispatch(Route $route, $controller, $method)
{
    $parameters = $this->resolveClassMethodDependencies(
        $route->parametersWithoutNulls(), $controller, $method
    );

    if (method_exists($controller, 'callAction')) {
            return $controller->callAction($method, $parameters);
    }

    return $controller->{$method}(...array_values($parameters));
}

剩下的事情就是 Controller控制器 的事了.

3.3 處理返回的 Response

Router 中有一個方法, 用於對返回的 $response 進行處理

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

/**
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
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);    # 最後的處理
}  

上述過程中, 在返回 $response 之前進行了最後的處理 $response->prepare($request)

該過程是在 Symfony\Component\HttpFoundation\Response::prepare() 中進行

對響應的封裝是通過 Illuminate\Http\Response 類完成, 該類底層是 Symfony 框架的 Response 類

即, Symfony\Component\HttpFoundation\Response

public function prepare(Request $request)
{
    $headers = $this->headers;

    if ($this->isInformational() || $this->isEmpty()) {
        $this->setContent(null);
        $headers->remove('Content-Type');
        $headers->remove('Content-Length');
    } else {
        // Content-type based on the Request
        if (!$headers->has('Content-Type')) {
            $format = $request->getRequestFormat();
            if (null !== $format && $mimeType = $request->getMimeType($format)) {
                $headers->set('Content-Type', $mimeType);
            }
        }

        // Fix Content-Type
        $charset = $this->charset ?: 'UTF-8';
        if (!$headers->has('Content-Type')) {
            $headers->set('Content-Type', 'text/html; charset='.$charset);
        } elseif (0 === stripos($headers->get('Content-Type'), 'text/') && false === stripos($headers->get('Content-Type'), 'charset')) {
            // add the charset
            $headers->set('Content-Type', $headers->get('Content-Type').'; charset='.$charset);
        }

        // Fix Content-Length
        if ($headers->has('Transfer-Encoding')) {
            $headers->remove('Content-Length');
        }

        if ($request->isMethod('HEAD')) {
            // cf. RFC2616 14.13
            $length = $headers->get('Content-Length');
            $this->setContent(null);
            if ($length) {
                $headers->set('Content-Length', $length);
            }
        }
    }

    // Fix protocol
    if ('HTTP/1.0' != $request->server->get('SERVER_PROTOCOL')) {
        $this->setProtocolVersion('1.1');
    }

    // Check if we need to send extra expire info headers
    if ('1.0' == $this->getProtocolVersion() && false !== strpos($this->headers->get('Cache-Control'), 'no-cache')) {
        $this->headers->set('pragma', 'no-cache');
        $this->headers->set('expires', -1);
    }

    $this->ensureIEOverSSLCompatibility($request);

    return $this;
}

4. 響應發送和程序終止

4.1 響應的發送

index.php 入口文件的最後是將響應返回給客戶端

$response->send();

Symfony\Component\HttpFoundation\Response

public function send()
{
    $this->sendHeaders();
    $this->sendContent();

    if (function_exists('fastcgi_finish_request')) {
        fastcgi_finish_request();
    } elseif (!\in_array(PHP_SAPI, array('cli', 'phpdbg'), true)) {
        static::closeOutputBuffers(0, true);
    }

    return $this;
}

public function sendHeaders()
{
    // headers have already been sent by the developer
    if (headers_sent()) {
        return $this;
    }

    // headers
    foreach ($this->headers->allPreserveCase() as $name => $values) {
        foreach ($values as $value) {
            header($name.': '.$value, false, $this->statusCode);
        }
    }

    // status
    header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText), true, $this->statusCode);

    return $this;
}

public function sendContent()
{
    echo $this->content;

    return $this;
}

4.2 請求中止

index.php 入口文件的最後:

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

依舊以 Http Kernel 爲例:

public function terminate($request, $response)
{
    $this->terminateMiddleware($request, $response);    # 中間件中止處理

    $this->app->terminate();    # 服務容器的中止處理函數
}

protected function terminateMiddleware($request, $response)
{
    $middlewares = $this->app->shouldSkipMiddleware() ? [] : array_merge(
        $this->gatherRouteMiddleware($request),
        $this->middleware
    );

    foreach ($middlewares as $middleware) {
        if (! is_string($middleware)) {
            continue;
        }

        list($name) = $this->parseMiddleware($middleware);

        $instance = $this->app->make($name);

        if (method_exists($instance, 'terminate')) {
            $instance->terminate($request, $response);
        }
    }
}

此處的中間件指的是定義在 Kernel 中的 $middleware 中間件數組列表, 不包含 路由中間件.

Laravel 5.1 注: 默認只有會話中間件包含 terminate() 函數

Application 服務容器的中止處理函數

public function terminate()
{
    foreach ($this->terminatingCallbacks as $terminating) {
        $this->call($terminating);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章