Laravel Session
PHP:7.2
Laravel:7.30.1
Laravel
做為一個 PHP Web Framework,對於 Session 的處理上也有許多著墨。
Architecture
在 Laravel
的 Session 中,有兩個概念需要知道:
Laravel Session
並不是使用PHP Native Session
Laravel
是透過Middleware
實作 Session 機制
Laravel
的 Session 會透過 StartSession
這個 middleware 啟用,可以在 app/Http/Kernel.php
裡發現 web Middleware Group
中有註冊 StartSession
的類別。
class Kernel extends HttpKernel
{
protected $middleware = [
...
];
protected $middlewareGroups = [
'web' => [
...
\Illuminate\Session\Middleware\StartSession::class,
...
],
'api' => [
...
],
];
...
}
How to Fix Laravel Session Problems
由於 Laravel Sessoin 與 PHP Sessoin 不同,以及在使用上需要知道一些細節才能正常運作,因此紀錄幾個較可能遇到的問題。
Session not working in Laravel API
許多人最常遇到的問題是 Laravel API 無法使用 Session
。
原因是因為 routes/api.php
底下的 Controller
預設是走 api Middleware Group
,因此裡面沒有 StartSession
去啟用 Session 機制。解決方法只要把 StartSession
註冊到 api Middleware Group
或乾脆往上註冊到最上層的 Middleware
中就行了。
Creating A New Session on Every Request
另一個常見的問題是,使用 Session 後,就算資料順利存入,每次重整頁面時都會拿到新的 Session。
有問題的 code
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function test(Request $request)
{
// 每次 refresh 都會拿到不同的 session ID
dd($request->session()->all());
if ($request->session()->has('name')) {
echo $request->session()->get('name');
} else {
$request->session()->put('name', 'BallBall');
}
return;
}
}
這部份可以先從 StartSession Middleware
開始看起。
vendor/laravel/framework/src/Illuminate/Session/Middleware/StartSession.php
protected function handleStatefulRequest(Request $request, $session, Closure $next)
{
$request->setLaravelSession(
$this->startSession($request, $session)
);
$this->collectGarbage($session);
$response = $next($request);
$this->storeCurrentUrl($request, $session);
// 將 Session 寫入 $response
$this->addCookieToResponse($response, $session);
$this->saveSession($request);
return $response;
}
這當中可以看到 StartSessoin
把 Session 寫到 Illuminate\Http\Response
的 Instance 裡。
而 Illuminate\Http\Response
是用來處理 HTTP Response 的物件,裡面包含 Header
、HTTP Status Code
等資訊。
因此,這個問題真正的原因是:
Controller
雖然預設是回傳 Illuminate\Http\Response
用來當 HTTP Response ,但是因為我們在回傳之前 先行輸出資料
,讓 Browser 認不出 Illuminate\Http\Response
提供的 Header
,因此前端沒有將 Cookie 存下,導致每次的訪問都是全新的訪問。
其實只要把 有問題的 code 當中的 dd($request->session()->all());
這段拿掉就可以正常運作了。
這問題看似簡單,其實要追蹤起來是滿複雜的,必須要知道原理才能通靈出原因。
Basic Usage
在知道一些原理和脈絡之後,其實用法是非常簡單的。
基本使用
// 判斷是否有值
if ($request->session()->has('name')) {
//
}
// 取得值
$request->session()->get('name');
// 取得 Session 所有資訊
$request->session()->all();
// 寫入值
$request->session()->put('name', 'BallBall');
// 也可以透過 global 的方法以 Array 的方式寫入
session([
'age' => 31
]);
// 刪除特定 key 的值
$request->session()->forget('age');
$request->session()->forget(['age', 'email']);
// 透過 push 寫入資料時,可以以 array 的形式寫入
$request->session()->push('user.env', 'develop');
$request->session()->push('user.env', 'staging');
$request->session()->push('user.env', 'production');
// 透過 pull 拿完資料以後,就會將值刪除
$request->session()->pull('name', 'default_value');
// 寫入 flash,下一個 request 後會被清空
$request->session()->flash('status', 'Logined');
// 將所有 flash session 重新寫進 flash,因此還可以再取得一次
$request->session()->reflash();
// 只針對特定的 key reflash
$request->session()->keep(['username', 'email']);
// 清空 Session
$request->session()->flush();
Configuration(Use memcached Driver)
Laravel Session
的相關設定都紀錄在 config/session.php
中。
其中的 session.driver
可以選擇將 session 存在什麼地方,像是 DB
、memcached
、redis
等等。
以將 Session 存在 memcached
為例,要修改以下設定。
Step 1:將 .env
中的 SESSION_DRIVER
改成 memcached
...
SESSION_DRIVER=memcached
...
Step 2:到 config/cache.php
中設定 memcached
的資訊
'memcached' => [
'driver' => 'memcached',
'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
'sasl' => [
env('MEMCACHED_USERNAME'),
env('MEMCACHED_PASSWORD'),
],
'options' => [
// Memcached::OPT_CONNECT_TIMEOUT => 2000,
],
'servers' => [
[
'host' => env('MEMCACHED_HOST', 'YOUR_URL'),
'port' => env('MEMCACHED_PORT', 11211),
'weight' => 100,
],
],
],
Notice: 或者在
.env
加入MEMCACHED_HOST
除了功能測試以外,可以透過檢查 storage/framework/sessions/
是否還有被寫入檔案來判斷是否改接成功。
另外,附帶說明一下,由於 Laravel Session
底層存放機制會用 Laravel Cache
實作,因此 driver 設定會需要去修改 config/cache.php
。