Laravel源碼分析——一次Http請求到響應

1前言

在FastCGI協議下工作的php-fpm, 使用持續的進程來處理一連串的請求, 具體到某個請求的解析流程的時候, 如果不考慮擴展的方式, 基本上都是順序的解析處理.

所以就算複雜如Laravel框架, 他也是一個順序的加載解析過程.

本地打斷點可以完整的走一遍框架的加載邏輯. 但是你可能並不一定十分理解爲什麼框架要這麼做? 或者他這一步究竟是幹嘛的?

本文就儘可能的提取Laravel處理http請求過程中一些關鍵的路徑跟大家分享一下Laravel框架工作的原理, 也方便後面大家自己排錯或者對於框架進行改造.

2預備知識

首先跟大家說的是後續在解析源碼的時候比較核心的兩個東西(某種意義上我覺得也是Laravel框架的骨架)——Container和Pipeline, 一個負責各種類實例的管理, 另一個負責流程的配置, 理解清楚了這兩個東西, Laravel框架的主體邏輯就會很好理解了. (如果之前已經有了解過的就可以略過了哈~)

3大總管-Container

後面會介紹到的Application類就是繼承自Container的一個框架運行過程中的實例的大總管.

大概的意思就類似如下的代碼:

所以後面在看源碼的時候凡是看到類似$app->make等都是在向大總管要一個對應的類示例.

4流水線-Pipeline

這裏說的是Laravel作者taylor自己開發的Pipeline 不是thephpleague的Pipeline

未命名1492144971.png

但是這兩個pipeline的庫都是很好的抽象庫——用來裝配定義對於某個數據源的解析流程

我們看下laravel框架裏的Pipeline的核心類Pipeline:可以看到該類實現了PipelineContract, 而該接口源碼如下


那麼這個接口主要描述的就是一個流水線需要實現的方法:

  • send方法: 將$traveler擺上這個流水線
  • through方法: 中間會經過幾個車間$stops
  • via方法: 經過每個車間的時候會被怎麼處理$method
  • then方法: 最終會被什麼車間處理$destination

那麼我們Pipeline類最終實現之後又有哪些調整呢?

  • send方法: 變量替換爲$passable
  • through方法: 變量替換爲$pipes
  • via方法: 變量$method默認賦值爲handle
  • [重點!!!]then方法明確的定義Laravel框架裏面的車間處理流程

5核心邏輯-then方法的實現

先看下源碼:

1188-640.jpg.jpg

第一行: 調用getInitialSlice傳入$destination得到最終車間對於$passable的處理方法$firstSlice

1189-640.jpg.jpg

第二行: 倒轉中間的$pipes的順序

第三行的代碼分開解析:

[第三行]$this->getSlice()

這裏得到的是一個車間的堆棧$stack以及當前車間$pipe對於$passable的一個處理邏輯:

如果$pipe是個閉包, 則直接調用傳入($passable, $stack)

如果$pipe是個對象, 則向大總管初始化, 然後調用handle方法處理($passable, $stack)

[第三行]array_reduce($pipes, $this->getSlice(), $firstSlice)

 核心的概要可以看如下的代碼:

那麼很明顯這裏我們就能得到一個已經裝配好的流水線——$this->getSlice()不斷的將最終的目的處理邏輯$firstSlice以及$pipes壓入這個處理流程的堆棧裏面, 最後得到這個按照順序的對於$passable的處理流程.

[第三行]return call_user_func( array_reduce($pipes, $this->getSlice(), $firstSlice), $this->passable);

最終將$passable放到這個流水線上處理並返回處理的結果

理解清楚了Pipeline之後我們下面分析源代碼就會快了很多, 因爲核心的邏輯都是依賴Pipeline來裝配的.

6源碼分析

這裏就順序的逐層來帶大家看下Laravel的http請求處理流程.

入口——public/index.php

對你沒有看錯, 其實Laravel處理http請求就這麼”幾行”代碼:

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

這裏引用這個文件得到一個Container的子類Application的示例,

同時app.php裏面還聲明瞭3個重要的抽象的實現

http請求的處理器Illuminate\Contracts\Http\Kernel::class實現爲App\Http\Kernel::class

console(命令行)請求的處理器Illuminate\Contracts\Console\Kernel::class實現爲App\Console\Kernel::class

異常Exception的處理器

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

之前只是配置, 這裏是真正得到一個new好的實例

下面就是比較關鍵的代碼了——處理器Kernel調用handle方法處理當前的請求$request得到\$response

我們從這個斷點深入看下處理器的handle方法

全局處理請求的handle方法——Illuminate\Foundation\Http\Kernel

App\Http\Kernel沒有handle方法, handle方法邏輯在他的父類Illuminate\Foundation\Http\Kernel裏面

大概掃了一眼, 正常邏輯的話其實就是這句——$response = $this->sendRequestThroughRouter($request);//將請求放到Router上面過一遍

所以我們繼續挖:

這裏就看到我們之前說的Pipiline的使用了——將請求$request放在可能有的$middleware中間件車間上處理, 最終送到$this->dispatchToRouter()上面處理, 那麼這個最終的目的車間不難想到應該就是當前請求匹配到的路由規則了, 所以我們繼續深挖:

1195-640.jpg.jpg

這裏利用了本身的router示例來進一步分發處理$request, 所以我們再新開一節看下Router是怎麼處理請求的

小結:

sendRequestThroughRouter中使用的中間件就是配置在App\Http\Kernel的全局中間件

然後我們看下文檔中關於中間件的用法說明就很清楚了:

1196-640.jpg.png

不難發現, handle方法其實就是一個處理$request數據的車間, 調用$next($request)之後就進入到了下一個車間, 所以中間件其實就是嵌套處理的邏輯, 通過劃分不同的層次實現邏輯解耦.

匹配特定路由規則的dispatch——Illuminate\Routing\Router

Router主要的職責就是找出匹配當前$request規則的路由並執行然後返回

1197-640.jpg.jpg

繼續看正常流程邏輯:$response = $this->dispatchToRoute($request);

附:

在$route = $this->findRoute($request);有一個我覺得可以優化的邏輯, 大家有興趣可以去看下

通過$route = $this->findRoute($request);我們得到了匹配當前請求的路由規則,

然後調用runRouteWithinStack來實際解析當前的請求

沒有眼花!又一個Pipeline的處理邏輯, 這裏的邏輯的就是:

真正的進入到了大家平時可能編寫的Controller的特定方法裏面去解析了

這裏的middleware就是大家路由裏面可能會配置的非全局的middleware了

收尾——回到public/index.php

正常的邏輯下就從這兩個Pipeline順利返回了$response

然後調用$response->send()將響應返回給客戶端, 之後處理器也停止運行.

一次http請求順利走完了整個Laravel框架

7總結

這就是我眼中Laravel的骨架——Container+PipeLine 以及 在這個骨架上搭建起來的一個處理http請求的流水線!

Laravel簡單嗎?——簡單, 他把框架搭好, 只需要我們填充自己的車間進去

Laravel複雜嗎?——複雜, Container後面的實現, PipeLine的沉澱, 都不是一下子憑空就能構造出來的.

通過Laravel框架的設計回過頭來看業務開發其實也是一樣的道理, 利用儘可能的高度簡單的抽象來概括某個領域, 然後將靈活多變的需求限定在這一個個”車間”裏面.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章