這兩天遇到了一個很奇怪的問題,更新session
,session
的值不變。經過一番追查,終於找到問題,並搞明白了原理。寫這篇部落格記錄下。
框架版本
Laravel 5.4
問題
先來描述下問題,我在我們專案基礎的Middleware
中,加入session
操作,存入了一個值,再在Controller
中取出使用,大致程式碼如下:
// Middleware
public function handle($request, Closure $next)
{
$id = Redis::get('id');
session(['id' => $id]);
return $next($request);
}
// Controller
public function index()
{
$id = session('id');
return ['id' => $id];
}
假設reids
中的id
是1,這一次訪問index
這個action
,返回的是1,當你將redis
中id
的值改成2時,在訪問,發現返回的還是1,而且之後的訪問也都是1。這裡說明一下session
使用的是redis
。
解決問題
看到這樣神奇的結果,百思不得其解。於是開啟Xdebug
,開始除錯。經過多次除錯,發現在執行完\Illuminate\Session\Middleware\StartSession
這個Middleware
後,session
裡面的值就變回1了,在之前都是2。然後想到會不會我們的Middleware
在StartSession
之前執行造成的,將我們的Middleware
移到StartSession
之後,發現果然可以了,app/Http/Kernel.php
中的程式碼如下:
protected $middlewareGroups = [
'web' => [
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\OurMiddleware::class,
]
];
其中的OurMiddleware
是我們自己寫的Middleware
,之前是放在最上面的,$next($request)
之前的程式碼的執行順序是從上到下的,如果OurMiddleware
中有些內容是必須在最開始的,可以考慮分成兩個Middleware
。
理解原理
雖然解決了問題,但還是不知道其原理究竟是怎樣的,帶著這樣的疑問我繼續檢視原始碼,最終找到了相應的內容。
-
session
不是實時落地的,也就是說當你呼叫session(['id' => $id])
時,id
並沒有被真正存入redis
中,而是快取在\Illuminate\Session\Store
單例的attributes
屬性中,可以檢視其put
方法,程式碼如下:public function put($key, $value = null) { if (! is_array($key)) { $key = [$key => $value]; } foreach ($key as $arrayKey => $arrayValue) { Arr::set($this->attributes, $arrayKey, $arrayValue); } }
-
\Illuminate\Session\Middleware\StartSession
在執行時,回自動載入redis
中已經例項化的資料,並覆蓋\Illuminate\Session\Store
單例中的attributes
屬性,所以這就導致我們一直取到的都是redis
中的session
資料。載入覆蓋的程式碼如下:protected function loadSession() { $this->attributes = array_merge($this->attributes, $this->readFromHandler()); } protected function readFromHandler() { if ($data = $this->handler->read($this->getId())) { $data = @unserialize($this->prepareForUnserialize($data)); if ($data !== false && ! is_null($data) && is_array($data)) { return $data; } } return []; }
其中的readFromHandler
方法就是獲取redis
中的session
資料。
後記
其實這不是Laravel session的坑,是我自己踩坑,原諒我是個標題黨:smile:
本作品採用《CC 協議》,轉載必須註明作者和本文連結