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 的物件,裡面包含 HeaderHTTP 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 存在什麼地方,像是 DBmemcachedredis 等等。

以將 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

Categories: Laravel