分析 Session(2)
先回顧一下類別圖,等等的說明搭著圖看會更好理解。
昨天有提到:
Laravel 所實作的五個 handler 不僅可以用在 SessionManager 上,也可以用在 PHP 內建的
$_SESSION
上
今天要來講原因了:因為沒有 StartSession
這個 middleware,的話,這個 SessionManager 是不會自動 work 的。但 SessionManager 又高度依賴 Laravel 內建的設定結構,(指 config/session.php
),因此如果要讓這個元件可以一般化(generalization)的話,最好的方法就是實作 SessionHandlerInterface
,這樣就能在所有 PHP 環境下使用了。
而這個設計也同時牽動了另一個設計:也就是讓 Store 聚合 SessionHandlerInterface 的設計,它們兩個的關係也是使用了 strategy pattern。
Strategy pattern 確實是一個符合開關原則(Open-close principle)的最佳實踐,但它同時也有一個嚴重的缺陷:這麼多 strategy,開發者要怎麼知道要使用哪一個,因此有兩種做法:直接使用,比方說直接把 FileSessionHandler
拿來用,這樣就容易違反最小知識原則(Least Knowledge Principle),因為類別間的知識,知道對方的細節越少越好,最好是只要依賴抽象 SessionHandlerInterface
就好,而不要依賴細節 FileSessionHandler
,因此有了第二種方法,就像 SessionManager 一樣,使用某個角色來管理這些 strategy,而這個方法則容易違反單一職責原則(Single responsibility principle),建構資訊容易集中在這個類別上,就會顯得很雜亂,因此或許大家也會覺得 SessionManager 的程式碼不一定好找,正是因為這個原因,而且是兩倍。因為 SessionManager
與 Session
的關係是 strategy pattern;Store
與 SessionHandlerInterface
的關係也是 strategy pattern。
原本或許只要 SessionManager
直接跟各種不同實作的 Store
做成 strategy pattern 就好,但因為 Laravel 對 Store 有自己一套處理介面,還有加密需求等,所以並不適合把 SessionHandlerInterface
直接實作在 Store
,所以才會演變成現在這樣的設計。
Laravel 如何知道來者何人?
Session 機制的原理是,使用一個隨機名稱,存放在 cookie 並設定過期時間,接著後端收到這個 cookie 的名稱後,以它為 key,在後端 Store 裡面取得對應儲存的資料。
PHP 內建的 session_start()
把這些實作都完成了,而 Laravel 則是自己刻了一套:也就是在 StartSession 這個 middleware 裡。Middleware 的原理在介紹 Pipeline 時,已經說明如何運行了,現在直接從 handle()
說明
public function handle($request, Closure $next) |
剛剛有提到 PHP 原生的 session 已經內建實作了寫 cookie 和儲存資料的行為,而這裡有趣的是,整個流程並沒有儲存 session 資料。那到底是什麼時候做呢?答案是 terminate()
:
public function terminate($request, $response) |
接著,另一個細節:cookie 的名稱是哪時決定的?這 startSession()
裡:
protected function startSession(Request $request) |
這個 tap() 的功能很特別,說明上有點困難,直接舉例它其實等價如下:
protected function startSession(Request $request) |
接著來看看 getSession()
是如何取得實例的:
public function getSession(Request $request) |
就是由 setId()
決定了 session key 的。裡面實作如下:
public function setId($id) |
所以在第一次進來的時候,就會產生新的 ID 了。
到目前為止,Session 的運作原理差不多就分析完畢了。
CookieSessionHandler
這是一個特殊的 handler,某些實作正是針對它而 workaround 的。首先我們會發現 StartSession 有個特別的方法 usingCookieSessions()
在判斷是不是這個 handler:
protected function usingCookieSessions() |
使用到它的時機有兩個,一個是 terminate()
時,另一個是 addCookieToResponse()
。在說明之前,先了解 CookieSessionHandler 實作:其實就是把 cookie 當作是存放 session 的空間。
還記得 bootstrap 流程,曾提過這段程式碼:
$response->send(); |
這會把網頁內容全都輸出到 client 上,所以顯而易見,terminate()
不需要儲存,而是要移到 addCookieToResponse()
在準備 cookie 的時候儲存。
因 CookieSessionHandler 會需要從 request 取得 cookie 資料,才有辦法解析出存放的 session 內容。因此會有一個方法 setRequestOnHandler()
是把 request 存放到 handler 裡,這也是特別為了它而寫的:
public function setRequestOnHandler($request) |
而其他的 handler 性質都差不多,所以會走一樣的流程。
今日總結
Session 雖然類別多,但結構算簡單,並且也有些設計理念存在,是個練習分析原始碼的好目標。