Laravel框架生命週期

wsAdmin發表於2022-05-31

概述

主要和大家分享一下Laravel生命週期相關文章,一起學習進步。原文出處

Laravel的生命週期開始於 public/index.php,結束於 public/index.php
客戶端的所有請求都經由Web伺服器引導到這個檔案中。

以下是public/index.php檔案的原始碼和註釋:

#public/index.php 檔案

// 定義了laravel一個請求的開始時間
define('LARAVEL_START', microtime(true));

// composer自動載入機制,載入專案依賴
require __DIR__.'/../vendor/autoload.php';

// 這句話你就可以理解laravel,在最開始引入了一個ioc容器。
$app = require_once __DIR__.'/../bootstrap/app.php';

// 這個相當於我們建立了Kernel::class的服務提供者
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

// 獲取一個 Request ,返回一個 Response。以把該核心想象作一個代表整個應用的大黑盒子,輸入 HTTP 請求,返回 HTTP 響應
// 處理請求
$response = $kernel->handle(
    // 建立請求例項
    $request = Illuminate\Http\Request::capture()
);

// 傳送響應,就是把我們伺服器的結果返回給瀏覽器。
$response->send();

// 終止程式,這個就是執行我們比較耗時的請求,
$kernel->terminate($request, $response);

由上可知 Laravel 的生命週期分為以下3個主要階段:

  • 載入專案依賴。
  • 建立Laravel應用例項。
  • 接收請求並響應。

接下來我們根據 public/index.php 檔案,詳細的說明一下

一、Composer 自動載入專案依賴

// composer自動載入機制
require __DIR__.'/../vendor/autoload.php';

Composer 是 PHP 的包管理器,用來管理專案依賴的工具,autoload.php 中引入了 Composer 自動生成的載入程式,實現載入第三方依賴。

autoload.php 檔案程式碼:

// autoload.php @generated by Composer

require_once __DIR__ . '/composer/autoload_real.php';

return ComposerAutoloaderInit101671ca9bbc2f62f8335eb842637291::getLoader();

二、建立應用例項

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

bootstrap/app.php 檔案程式碼:

// 建立Laravel應用的ioc容器,
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
// 完成核心的繫結
$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
);
// 返回例項
return $app;

如果不理解,可以先去學習一下Laravel框架的 Ioc 容器(服務容器)
Ioc 容器(服務容器) 是 Laravel 的核心,這一階段主要實現了 2大功能:
1、建立容器
2、繫結核心

建立容器

$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

我們進入 Illuminate\Foundation\Application 容器中,以下是建構函式的分析:

    /**
     * Create a new Illuminate application instance.
     *
     * @param  string|null  $basePath
     * @return void
     */
    public function __construct($basePath = null)
    {
        if ($basePath) {
            // 1、路徑繫結
            $this->setBasePath($basePath);
        }
        // 2、基礎繫結
        $this->registerBaseBindings();
        // 3、基礎服務提供者繫結(事件,日誌,路由)
        $this->registerBaseServiceProviders();
        // 4、核心別名繫結,目的是給核心的類名稱空間設定別名,以便後續使用
        $this->registerCoreContainerAliases();
    }

繫結核心

// 完成核心的繫結
// HTTP核心
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);
// Console核心
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);
// 繫結異常處理
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

在 Laravel 中只要是透過 public/index.php 來啟動框架的,都會用到 Http Kernel(主要作用就是接受請求並返回響應),而其他的例如透過 artisan 命令、計劃任務、佇列等啟動框架進行處理的都會用到 Console 核心。
其中 HTTP 核心類繼承 Illuminate\Foundation\Http\Kernel ,HTTP核心類中定義了中介軟體相關陣列,中介軟體主要是提供了一種方便機制用來過濾進入應用的請求和處理HTTP響應。

HTTP 核心類:

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    ];

    /**
     * The priority-sorted list of middleware.
     *
     * This forces non-global middleware to always be in the given order.
     *
     * @var array
     */
    protected $middlewarePriority = [
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\Authenticate::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];
}

HTTP核心類的父類:Illuminate\Foundation\Http\Kernel 提供了名為 $bootstrappers 的載入程式陣列,包括了環境檢測、載入配置、處理異常、Facades註冊、服務提供者註冊、啟動服務這6個程式。

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    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,
    ];

Console 核心:

一個健壯的應用程式除了滿足網路請求外,還應該包括執行計劃任務、非同步佇列等,Laravel中透過artisan工具定義各種命令來完成非 HTTP 請求的各種場景。artisan命令透過 Laravel 的 Console核心完成對應用核心元件的排程來完成任務。

繫結異常處理

異常處理由 App\Exceptions\Handler 類完成,所有的異常處理都由其處理,在這裡同樣不做詳細介紹。

三、接收請求並響應

解析核心

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

這裡將 HTTP 核心解析出來,我們進一步檢視Illuminate\Contracts\Http\Kernel::class 的內部程式碼。
建構函式中注入了app容器和路由器2個函式,在例項化核心的過程中,將核心中定義的中介軟體註冊到路由器中,註冊完成後便可以處理HTTP請求前呼叫中介軟體 實現過濾請求的目的。

Illuminate\Foundation\Http\Kernel 的建構函式:

    /**
     * Create a new HTTP kernel instance.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @param  \Illuminate\Routing\Router  $router
     * @return void
     */
    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);
        }
    }

    // Illuminate\Routing\Router 類中

    /**
     * Register a group of middleware.
     *
     * @param  string  $name
     * @param  array  $middleware
     * @return $this
     */
    public function middlewareGroup($name, array $middleware)
    {
        $this->middlewareGroups[$name] = $middleware;

        return $this;
    }

    /**
     * Register a short-hand name for a middleware.
     *
     * @param  string  $name
     * @param  string  $class
     * @return $this
     */
    public function aliasMiddleware($name, $class)
    {
        $this->middleware[$name] = $class;

        return $this;
    }

處理 HTTP 請求

// handle 方法處理請求
$response = $kernel->handle(
    // 建立請求例項
    $request = Illuminate\Http\Request::capture()
);

建立請求例項:在處理請求過程之前會透過 Illuminate\Http\Request 的 capture 方法,以進入應用的HTTP請求資訊為基礎,建立出一個 Laravel Request請求例項,在後續應用剩餘的生命週期中Request請求例項就是對本次HTTP請求的抽象。

    /**
     * Create a new Illuminate HTTP request from server variables.
     *
     * @return static
     */
    public static function capture()
    {
        static::enableHttpMethodParameterOverride();

        return static::createFromBase(SymfonyRequest::createFromGlobals());
    }

    /**
     * Creates a new request with values from PHP's super globals.
     *
     * @return static
     */
    public static function createFromGlobals()
    {
        $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER);

        if (0 === strpos($request->headers->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded')
            && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH'])
        ) {
            parse_str($request->getContent(), $data);
            $request->request = new ParameterBag($data);
        }

        return $request;
    }

handle 處理請求:

將 HTTP 請求抽象成 Laravel Request 請求例項後,請求例項會被傳導進入到HTTP核心的 handle 方法內部,請求的處理就是由 handle方法來完成的。

namespace Illuminate\Foundation\Http;

class Kernel implements KernelContract
{
    /**
     * Handle an incoming HTTP request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    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;
    }
}

handle 方法接收了一個請求,並在最後返回了一個 HTTP響應。

接下來進入 sendRequestThroughRouter 方法,透過中介軟體/路由器傳送給定的請求。

該方法的程式執行如下:
1、將 request 請求例項註冊到 app 容器當中
2、清除之前的 request 例項快取
3、啟動載入程式:載入核心中定義的載入程式來引導啟動應用
4、將請求傳送到路由:透過 Pipeline物件傳輸 HTTP請求物件流經框架中定義的HTTP中介軟體和路由器來完成過濾請求,最終將請求傳遞給處理程式(控制器方法或者路由中的閉包)由處理程式返回相應的響應。

    /**
     * Send the given request through the middleware / router.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\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());
    }

bootstrap 載入程式

    /**
     * Bootstrap the application for HTTP requests.
     *
     * @return void
     */
    public function bootstrap()
    {
        if (! $this->app->hasBeenBootstrapped()) {
            $this->app->bootstrapWith($this->bootstrappers());
        }
    }

    /**
     * Get the bootstrap classes for the application.
     *
     * @return array
     */
    protected function bootstrappers()
    {
        return $this->bootstrappers;
    }

    /**
     * The bootstrap classes for the application.
     *
     * @var array
     */
    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,
    ];

    /*
    引導啟動Laravel應用程式
    1. DetectEnvironment  檢查環境
    2. LoadConfiguration  載入應用配置
    3. ConfigureLogging   配置日至
    4. HandleException    註冊異常處理的Handler
    5. RegisterFacades    註冊Facades 
    6. RegisterProviders  註冊Providers 
    7. BootProviders      啟動Providers
    */

關於載入程式的啟動原理,大家可以自己詳細的去了解一下。

傳送響應

$response->send();

經過了以上階段,終於獲取到了我們想要的資料,接下來是將資料響應到客戶端。

程式由在 Illuminate\Http\Response 內部由其父類 Symfony\Component\HttpFoundation\Response 的 send() 方法實現。

    /**
     * Sends HTTP headers and content.
     *
     * @return $this
     */
    public function send()
    {
        $this->sendHeaders(); // 傳送頭部資訊
        $this->sendContent(); // 傳送報文主題

        if (\function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        } elseif (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
            static::closeOutputBuffers(0, true);
        }
        return $this;
    }

四、終止應用程式

該過程呼叫終止中介軟體。

響應完成後,HTTP 核心會呼叫 terminate 中介軟體做一些後續的處理工作。比如,Laravel 內建的 session 中介軟體會在響應傳送到瀏覽器之後將會話資料寫入儲存器中。

HTTP 核心的 terminate 方法會呼叫 terminate 中介軟體的 terminate 方法,呼叫完成後,整個生命週期結束。

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

    /**
     * Call the terminate method on any terminable middleware.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    public function terminate($request, $response)
    {
        $this->terminateMiddleware($request, $response);

        $this->app->terminate();
    }

    /**
     * Call the terminate method on any terminable middleware.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Illuminate\Http\Response  $response
     * @return void
     */
    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;
            }

            [$name] = $this->parseMiddleware($middleware);

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

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

五、總結

Laravel 的整個生命週期中,載入專案依賴、建立應用例項、接收並響應請求,終止程式,核心都起到了串聯作用。

  • 首先,建立 Laravel 應用程式 階段時,包含了註冊專案基礎服務、註冊專案服務提供者別名、註冊目錄路徑、異常類繫結等工作,同時在HTTP 核心中配置了載入程式。
  • 接著,在 接收請求並響應 階段時,會根據執行的環境解析出 HTTP 核心或 Console 核心。在 HTTP 核心中把中介軟體註冊到路由器中。
  • 然後,處理請求階段的過程中,將請求的例項註冊到app容器中,透過載入程式啟動應用,最後傳送請求到路由。之後,透過Response類響應資料。最後,透過terminate 方法終止程式。

以上就是關於 Laravel 生命週期 的詳細解析。

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

相關文章