Laravel 功能相當強大且實用,本篇文章將逐步探索 Laravel 核心生命週期機制和註解理解的流程和功能,不難發現其實核心設計是相當巧妙且具有高彈性和客製化,從 public/index.php 進入點出發,理解其核心運作原理和概念會有助於後續客製化和開發,文末會簡單列出核心步驟方便大家在設計代碼時參考。

Entry point

// public/index.php
<?php

use Illuminate\Contracts\Http\Kernel;
use Illuminate\Http\Request;

// 定義了常數 LARAVEL_START,其值為當前時間的微秒數,用於計算應用程式的啟動時間
define('LARAVEL_START', microtime(true));

// 檢查應用程式是否處於維護模式。
// 如果存在,則引入該檔案。維護模式用於暫時關閉應用程式,以進行維護或升級。
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
    require $maintenance;
}

// 用於自動載入所需的依賴項和類別
require __DIR__.'/../vendor/autoload.php';

// 這邊會細說,這邊會返回 app instance
$app = require_once __DIR__.'/../bootstrap/app.php';

// 使用 Service Container 解析實作 Http Kernel 接口的實例。
$kernel = $app->make(Kernel::class);

// 使用 Request::capture() 捕獲了當前的請求,並將其傳遞給 handle() 方法進行處理。handle() 方法處理請求並返回一個回應對象,並將其賦值給 $response 變數。
$response = $kernel->handle(
    $request = Request::capture()
)->send();

// 使用 terminate() 方法通知 Kernel 實例已經處理完畢,並傳遞了原始請求和回應對象。這個方法在應用程式執行結束後執行,例如在回應被送出到瀏覽器之後。
$kernel->terminate($request, $response);

核心 Application: Service Container

上面代碼是講述 Laravel 整個生命週期運行的步驟,接下來說明 app.php 的運作流程:

<?php

// 建立 Application instance
// dirname(__DIR__) => 專案根目錄
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);

// singlton: 綁定抽象 Contracts\Http\Kernel 到 App\Http\Kernel
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// singlton: 綁定抽象 Contracts\Console\Kernel 到 App\Console\Kernel
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// singlton: 綁定抽象 Contracts\Debug\ExceptionHandler 到 App\Exceptions\Handler
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

// 返回 Application instance
return $app;

這邊開始說明 Illuminate\Foundation\Application細節:

class Application extends Container implements ApplicationContract, CachesConfiguration, CachesRoutes, HttpKernelInterface
{
//...

這邊可以看到 Application 繼承了 Container 與實作 ApplicationContract 接下來看裡面具體實作與接口:

// Illuminate\Contracts\Foundation\Application
use Illuminate\Contracts\Container\Container;

interface Application extends Container
{
//...

use Psr\Container\ContainerInterface;
interface Container extends ContainerInterface
{
//...

這邊可以看到接口 Container 又繼承了 Psr\Container\ContainerInterface 這是 PSR 定義 Container 的接口,讓我們看看裡面:

interface ContainerInterface
{
	// 透過 id 找 container
    public function get(string $id);

	// id 判斷 container 是否存在
    public function has(string $id): bool;
}

至此如果要實現 Laravel 的 Application 就需要實現三個接口,分別是:

  • Application
  • Container
  • ContainerInterface

裡面方法簡單說明大致上用途:

  • Application: 定義 Laravel 版本與生命週期鉤子、解析 provider 等接口
  • Container: 容器的核心功能,包含 bind 與 instance
  • ContainerInterface: PSR 所定義的取得與判斷是否為容器的接口

接著回到 Illuminate\Contracts\Foundation\Application 繼續查看 Container 類內部的實現:

class Container implements ArrayAccess, ContainerContract
{
//...

這邊也可以看到 Container 本身也是再度實現了 ContainerContract 這代表 Application 是 Container 的超集,你也可以依據需求去替換 Container。 回到 Application 這邊就是具體實現了 Laravel 框架的 Service Container 運作以及需要具備的功能,包含啟動 provider 等。

至此我們能知道 Application 就是 Laravel 當中主要核心 Service Container 的實現,圍繞著這個超集開始建構生命週期、提供者等相關功能。 在 Application 建構時會載入必要的 Service provider 以進行後續動作:

public function __construct($basePath = null)
{
	// 預設 base path => 專案根目錄
	// dirname(__DIR__)
    if ($basePath) {
        $this->setBasePath($basePath);
    }

    $this->registerBaseBindings();
    $this->registerBaseServiceProviders();
    $this->registerCoreContainerAliases();
}

接下來來看看 registerBaseBindings 具體裡面的動作:

protected function registerBaseBindings()
{
	// 設置了當前應用程序實例為 $this
    static::setInstance($this);

	// 將當前應用程序實例綁定到容器陣列中的 app key [app=>$this]
    $this->instance('app', $this);

	// 將當前應用程序實例綁定到容器陣列中的 Container::class key [Container::class=>$this]
    $this->instance(Container::class, $this);
	// 用來解析 laravel mix mix-manifest
    $this->singleton(Mix::class);

	// 綁定PackageManifest::class, 讀取 composer packages manifest,如果有 cache path 就取得 cache path 的 mapping 不需要再去取 vendor path
    $this->singleton(PackageManifest::class, fn () => new PackageManifest(
        new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
    ));
}

下一個 registerBaseServiceProviders 則是開始註冊基礎的 service provider:

$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));

最後在 registerCoreContainerAliases alias 核心 container 的項目,綁定個別名方便調用:

public function registerCoreContainerAliases()
{
    foreach ([
        'app' => [self::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 別名關聯到 CacheManager 和接口 Cache,可以使用 $this->app['cache'] 或是 app()->get('cache') 取得 CacheManager 實例
        'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
//....

接下來我們來看下面單例模式綁定的幾個核心功能:

// singlton: 綁定抽象 Contracts\Http\Kernel 到 App\Http\Kernel
$app->singleton(
    Illuminate\Contracts\Http\Kernel::class,
    App\Http\Kernel::class
);

// singlton: 綁定抽象 Contracts\Console\Kernel 到 App\Console\Kernel
$app->singleton(
    Illuminate\Contracts\Console\Kernel::class,
    App\Console\Kernel::class
);

// singlton: 綁定抽象 Contracts\Debug\ExceptionHandler 到 App\Exceptions\Handler
$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

這時候會有個疑問這邊為何會單獨提取出來作綁定而不是在 application 直接 singleton,是因為有些情況下我們會需要替換或調整,另一方面在幾個方法有調用到基礎綁定的功能,也就是說要等前面那些功能綁定後才能在 Http Kernel, Console Kernel, Exception Handler 才能使用。 最後返回 Application instance:

// ...
return $app;

Http Kernel

接下來我們來看:

// Illuminate\Contracts\Http\Kernel
// 解析實作 Kernel 接口綁定的 kernel:App\Http\Kernel
$kernel = $app->make(Kernel::class);

最後我們來看處理請求的部分:

// 使用 Request::capture() 捕獲了當前的請求,並將其傳遞給 handle() 方法進行處理。handle() 方法處理請求並返回一個回應對象,並將其賦值給 $response 變數。
$response = $kernel->handle(
    $request = Request::capture()
)->send();

// 使用 terminate() 方法通知 Kernel 實例已經處理完畢,並傳遞了原始請求和回應對象。這個方法在應用程式執行結束後執行,例如在回應被送出到瀏覽器之後。
$kernel->terminate($request, $response);

歸納流程

  1. 定義啟動時間
  2. 檢查是否為維護模式
  3. 載入 composer vendor
  4. 實例化 Application
  5. 取得實作 Http Kernel 接口的實例 HttpKernel
  6. 處理當前請求跟回應
  7. 終結當前請求和回應
  8. 生命週期結束

總結

針對 Laravel 核心生命週期旅程到這裡就結束了,當然當中還有許多處理細節,如果有興趣的話可以閱讀細節代碼了解各個狀態跟流程,不難發現如果以宏觀角度來說其實設計相當流暢且優美的,理解了這樣子的設計不論在後續開發或是自己想要設計時都可以帶來不同的想法。