本文嘗試在程式碼的角度對 Laravel 的生命週期進行說明。
概括來說 Laravel 的生命週期由以下幾個階段組成:
- Application 初始化;
- Kernel 和 Exception Handler 註冊到Application容器物件上,Kernel 分為 HttpKernel 和 ConsoleKernel;
- 使用 Application 物件建立 Kernel;
- 建立 Request 物件來包裝客戶端相關的請求資料;
- 把 Request 物件交予 Kernel 的 handle 方法處理,生成 Response 物件;
- 使用 Response 的 send 方法,把相關的輸出返回給客戶端;
- 呼叫 Kernel 的 terminate 方法進行相關的清理操作,至此整個請求結束。
上面在巨集觀的層面介紹了 Laravel 的生命週期,每個生命週期都實現了特定的功能,關於這些功能的細節將在下面的進行初步介紹。
Application 初始化
Application 是 Laravel 中最為核心的物件,如果沒有它 Laravel 中的一切物件就都將不存在。
Application 物件是一個 應用 容器 物件。換句話說它即是一個容器也是一個應用程式物件。作為一個表示「應用」的物件,提供了應用的基本資訊,例如:路徑相關,當前執行環境等相關基礎資訊;作為一個「容器」物件,提供了相關物件註冊,建立,儲存等相關的功能,在 Laravel 中各個元件中都可以非常方便的獲取當前的 Application 物件,所以可以簡單簡單粗暴的把 Application 理解為一個全域性的單例物件,所有需要全域性共享的資料都可以儲存到 Application 上。
Application 物件的初始化過程如下:
定義專案的目錄結構
設定基礎路徑 $basePath
,以 $basePath
路徑為基礎, 定義了專案目錄結構 ,並且把相關的目錄結構儲存到 Application 物件上。下文以./
表示基礎路徑 $basePath
具體的目錄結構如下:
path
,對應的目錄為./app
,裡面包含了專案相關的核心程式碼,例如Controler
和Middleware
都在這一層;path.base
,目錄為./
,專案的基礎目錄;path.resources
,目錄為./resources
, 模版檔案和沒有被編譯過的css和js原始碼,圖片等相關資原始檔放這個這個目錄,如果應用程式設計到多語音,那麼多語言檔案也在這個目錄;path.lang
,目錄為./resources/lang
,多語言配置檔案所在的目錄;path.public
,目錄為./public
, web server 的根目錄,web server 只能訪問到這個目錄,主要包含了專案的入口檔案 index.php,編譯好的 css 和 js 等相關資原始檔都要拷貝的這個目錄。path.config
,目錄為./config
,配置檔案所在的目錄,path.storage
目錄為./storage
,這個目錄需要寫入的許可權,主要包含了3類在專案執行的過程中會產生的檔案:快取檔案,session 檔案,日誌檔案。path.database
,目錄為./database
,這個目錄存放了資料庫相關的檔案,主要分為3類:資料庫結構生成器檔案,種子資料檔案,建立種子資料工廠類檔案。path.bootstrap
,目錄為./bootstrap
,這個目錄包含了最外層的引導檔案app.php
,這個檔案建立了 Application 物件,該目錄的./bootstrap/cache
還生成了快取ServiceProvder
配置檔案
儲存基礎例項物件到 Application 上
設定了容器的單例物件,然後儲存了兩個例項物件到 Application 。
static::setInstance($this);
把當前 Application 物件作為容器單例儲存到容器$this->instance(Container::class, $this);
把當前 Application 物件儲存到容器- 建立
PackageManifest
物件,並把該資料儲存到 Application 物件,這個物件的主要作用是分析./vendor/installed.json
檔案,把 laravel 相關的配置讀取出來,例如 ServiceProvider等
註冊基礎的服務提供者(Service Provider)
註冊了 3 個基礎的服務提供者。
- EventServiceProvider,事件模組,事件模組是 Laravel 實現可擴充套件性的一個重要的工具;
- LogServiceProvider,日誌模組;
- RoutingServiceProvider,路由模組,該模組的主要作用是把請求轉發到具體的處理器上;
定義核心容器的相關別名
本質來說就是把別名和真正的類之間做了一層對映關係,我覺得這個別名系統的缺點大於優點,優點無非就是所謂的寫起來“簡潔”,“簡潔”的同時會帶來閱讀上的不直觀,缺點首先是需要迴圈去定義對映關係,其實是這個對映關係(陣列)需要使用額外的記憶體是儲存。
Kernel 的建立
這裡只介紹 HttpKernel 相關的功能和初始化的過程。
Kernel 是位於 Application 上層的物件,相關的初始化操作和請求的處理都是由 Kernel 來處理的。
初始化的過程
Kernel 是使用 Application 容器物件建立的。
- Kernel 的建構函式定義了 Application 和 Router 型別的引數,這兩個引數在 Application 都在已經在 Application 上註冊或者建立好了;
- 把
$middlewarePriority
賦值給 Router 物件,這類中介軟體由優先順序的設定; - 把
$routeMiddleware
定義的分組中介軟體賦值給 Router 物件; - 把
$routeMiddleware
別名儲存到 Router 物件上。
根據上面幾個步驟來看,中介軟體的執行都是在 Router 上實現的,Kernel 只是負責定義而已。
Kernel 的 Handle 方法執行的流程
Handle 方法的引數需求是需要一個 Request
物件,這個 Request
物件在 index.php
已經建立。
具體的流程如下:
-
設定
Request
物件的 http 方法是可以通過_method
引數覆蓋的,目前我還發現這個功能的使用實際的使用場景; -
把
Request
物件儲存的 Application 容器裡; -
執行
Kernel
上定義的引導器列表,相關的自定義的初始化操作都是通過這個步驟來實現的, 比如:環境變數和配置檔案的載入;服務提供者的註冊和引導等; -
建立
Pipeline
物件,把Request
物件交給Pipeline
物件處理;處理順序先由$middleware
屬性定義的中介軟體列表處理完之後交給 Router 物件處理,處理完成之後會生成一個Response
物件; -
異常的處理,新版 PHP 所有可以丟擲/捕獲的異常都實現了
Throwable
介面,首先對Exception
異常進行捕獲,然後交給renderException
方法去處理返回, 對於非Exception
的可丟擲/捕獲的異常(實際就是Error)會把它包裝為FatalThrowableError
物件,然後再給renderException
方法處理返回; -
最後派發
RequestHandled
事件, 該事件表明對Request
的相關處理已經完成, 事件系統已經在 Application 物件初始化的時候已經建立,所以這裡可以直接用 Application 取出來使用。
在上面介紹的步驟中其中一個叫做「執行 Kernel
上定義的 $bootstrapers
列表」。下面對相關的 bootstrapers
列表進行說明。
$bootstrappers
(引導器) 列表
引導器具體的執行者是 Application 應用程式物件,為避免多次執行,Application 會在執行完成之後進行標記為已經引導。具體的引導過程如下:
- 標記
$hasBeenBootstrapped
屬性為true
; - 遍歷引導器列表,每個引導器在之後之前會派發
bootstrapping:引導器名稱
的事件; - 然後使用 Application 容器物件建立引導器物件,然後呼叫引導器物件的
bootstrap
方法; - 最後派發
bootstrapped:引導器名稱
的事件。
透過事件系統,來實現可擴充套件性在這裡就能體現出來。
具體定義的引導器列表為:
- LoadEnvironmentVariables, 載入「.env」 檔案中定義的環境變數;
- LoadConfiguration,載入配置檔案;
- HandleExceptions,設定相關係統級別的異常處理;
- RegisterFacades,註冊 Facades 列表;
- RegisterProviders,載入註冊服務提供者(Service Provider);
- BootProviders,執行已經註冊的服務提供者的
boot
方法;
至此相關的引導器已經執行完成,在後面的文章中會對每個引導器詳細的介紹。
本作品採用《CC 協議》,轉載必須註明作者和本文連結