LaravelDocs(中文)

Session

Laravel 透過統一的 API 支援多種 Session 後端

簡介 (Introduction)

由於 HTTP 驅動的應用程式是無狀態的,Session 提供了一種在多個請求之間儲存使用者資訊的方式。該使用者資訊通常會放置在可從後續請求存取的持久性儲存 / 後端中。

Laravel 內建了多種 Session 後端,可透過富有表現力的統一 API 進行存取。支援流行的後端,如 MemcachedRedis 和資料庫。

設定 (Configuration)

您的應用程式的 Session 設定檔儲存在 config/session.php。請務必查看此檔案中可用的選項。預設情況下,Laravel 設定為使用 database Session 驅動程式。

Session driver 設定選項定義了每個請求的 Session 資料將儲存在何處。Laravel 包含多種驅動程式:

  • file - Session 儲存在 storage/framework/sessions
  • cookie - Session 儲存在安全、加密的 Cookie 中。
  • database - Session 儲存在關聯式資料庫中。
  • memcached / redis - Session 儲存在這些快速、基於快取的儲存中。
  • dynamodb - Session 儲存在 AWS DynamoDB 中。
  • array - Session 儲存在 PHP 陣列中,不會被持久化。

[!NOTE] array 驅動程式主要在 測試 期間使用,可防止儲存在 Session 中的資料被持久化。

驅動程式先決條件 (Driver Prerequisites)

資料庫 (Database)

使用 database Session 驅動程式時,您需要確保有一個資料庫資料表來包含 Session 資料。通常,這包含在 Laravel 的預設 0001_01_01_000000_create_users_table.php 資料庫 Migration 中;但是,如果您因為任何原因沒有 sessions 資料表,您可以使用 make:session-table Artisan 指令來產生此 Migration:

php artisan make:session-table

php artisan migrate

Redis

在 Laravel 中使用 Redis Session 之前,您需要透過 PECL 安裝 PhpRedis PHP 擴充功能,或透過 Composer 安裝 predis/predis 套件 (~1.0)。有關設定 Redis 的更多資訊,請參閱 Laravel 的 Redis 文件

[!NOTE] > SESSION_CONNECTION 環境變數或 session.php 設定檔中的 connection 選項可用於指定哪個 Redis 連線用於 Session 儲存。

與 Session 互動 (Interacting With The Session)

取得資料 (Retrieving Data)

在 Laravel 中有兩種主要的方式來處理 Session 資料:全域 session 輔助函式和透過 Request 實例。首先,讓我們看看透過 Request 實例存取 Session,它可以在路由 Closure 或 Controller 方法上進行型別提示。請記住,Controller 方法的依賴項會透過 Laravel Service Container 自動注入:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function show(Request $request, string $id): View
    {
        $value = $request->session()->get('key');

        // ...

        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

當您從 Session 取得項目時,您也可以將預設值作為第二個參數傳遞給 get 方法。如果指定的鍵在 Session 中不存在,將返回此預設值。如果您將 Closure 作為預設值傳遞給 get 方法,且請求的鍵不存在,則會執行該 Closure 並返回其結果:

$value = $request->session()->get('key', 'default');

$value = $request->session()->get('key', function () {
    return 'default';
});

全域 Session 輔助函式 (The Global Session Helper)

您也可以使用全域 session PHP 函式來取得和儲存 Session 中的資料。當使用單一字串參數呼叫 session 輔助函式時,它將返回該 Session 鍵的值。當使用鍵 / 值對陣列呼叫輔助函式時,這些值將儲存在 Session 中:

Route::get('/home', function () {
    // Retrieve a piece of data from the session...
    $value = session('key');

    // Specifying a default value...
    $value = session('key', 'default');

    // Store a piece of data in the session...
    session(['key' => 'value']);
});

[!NOTE] 透過 HTTP 請求實例使用 Session 與使用全域 session 輔助函式之間幾乎沒有實際差異。這兩種方法都可以透過所有測試案例中都可用的 assertSessionHas 方法進行 測試

取得所有 Session 資料 (Retrieving All Session Data)

如果您想取得 Session 中的所有資料,您可以使用 all 方法:

$data = $request->session()->all();

取得部分 Session 資料 (Retrieving A Portion Of The Session Data)

onlyexcept 方法可用於取得 Session 資料的子集:

$data = $request->session()->only(['username', 'email']);

$data = $request->session()->except(['username', 'email']);

判斷項目是否存在於 Session 中 (Determining If An Item Exists In The

Session)

要判斷項目是否存在於 Session 中,您可以使用 has 方法。如果項目存在且不為 nullhas 方法返回 true

if ($request->session()->has('users')) {
    // ...
}

要判斷項目是否存在於 Session 中,即使其值為 null,您可以使用 exists 方法:

if ($request->session()->exists('users')) {
    // ...
}

要判斷項目是否不存在於 Session 中,您可以使用 missing 方法。如果項目不存在,missing 方法返回 true

if ($request->session()->missing('users')) {
    // ...
}

儲存資料 (Storing Data)

要在 Session 中儲存資料,您通常會使用請求實例的 put 方法或全域 session 輔助函式:

// Via a request instance...
$request->session()->put('key', 'value');

// Via the global "session" helper...
session(['key' => 'value']);

推送到陣列 Session 值 (Pushing To Array Session Values)

push 方法可用於將新值推送到作為陣列的 Session 值。例如,如果 user.teams 鍵包含團隊名稱的陣列,您可以像這樣將新值推送到陣列中:

$request->session()->push('user.teams', 'developers');

取得並刪除項目 (Retrieving Deleting An Item)

pull 方法將在單一語句中從 Session 取得並刪除項目:

$value = $request->session()->pull('key', 'default');

遞增和遞減 Session 值 (Incrementing And Decrementing Session Values)

如果您的 Session 資料包含您希望遞增或遞減的整數,您可以使用 incrementdecrement 方法:

$request->session()->increment('count');

$request->session()->increment('count', $incrementBy = 2);

$request->session()->decrement('count');

$request->session()->decrement('count', $decrementBy = 2);

快閃資料 (Flash Data)

有時您可能希望在 Session 中為下一個請求儲存項目。您可以使用 flash 方法來實現。使用此方法儲存在 Session 中的資料將立即可用,並在後續的 HTTP 請求期間可用。在後續的 HTTP 請求之後,快閃資料將被刪除。快閃資料主要用於短期的狀態訊息:

$request->session()->flash('status', 'Task was successful!');

如果您需要為多個請求保留快閃資料,您可以使用 reflash 方法,這將為額外的請求保留所有快閃資料。如果您只需要保留特定的快閃資料,您可以使用 keep 方法:

$request->session()->reflash();

$request->session()->keep(['username', 'email']);

要僅為當前請求保留您的快閃資料,您可以使用 now 方法:

$request->session()->now('status', 'Task was successful!');

刪除資料 (Deleting Data)

forget 方法將從 Session 中移除一段資料。如果您想從 Session 中移除所有資料,您可以使用 flush 方法:

// Forget a single key...
$request->session()->forget('name');

// Forget multiple keys...
$request->session()->forget(['name', 'status']);

$request->session()->flush();

重新產生 Session ID (Regenerating The Session Id)

重新產生 Session ID 通常是為了防止惡意使用者利用 Session 固定 攻擊您的應用程式。

如果您使用 Laravel 應用程式 Starter KitsLaravel Fortify,Laravel 會在驗證期間自動重新產生 Session ID;但是,如果您需要手動重新產生 Session ID,您可以使用 regenerate 方法:

$request->session()->regenerate();

如果您需要在單一語句中重新產生 Session ID 並從 Session 中移除所有資料,您可以使用 invalidate 方法:

$request->session()->invalidate();

Session 快取 (Session Cache)

Laravel 的 Session 快取提供了一種方便的方式來快取限定於個別使用者 Session 的資料。與全域應用程式快取不同,Session 快取資料會自動按 Session 隔離,並在 Session 過期或銷毀時清理。Session 快取支援所有熟悉的 Laravel 快取方法,如 getputrememberforget 等,但範圍限定於當前 Session。

Session 快取非常適合儲存您希望在同一 Session 內的多個請求之間持久保存,但不需要永久儲存的臨時、使用者特定資料。這包括表單資料、臨時計算、API 回應或任何其他應該與特定使用者 Session 綁定的短暫資料。

您可以透過 Session 上的 cache 方法存取 Session 快取:

$discount = $request->session()->cache()->get('discount');

$request->session()->cache()->put(
    'discount', 10, now()->addMinutes(5)
);

有關 Laravel 快取方法的更多資訊,請參閱 快取文件

Session 阻塞 (Session Blocking)

[!WARNING] 要使用 Session 阻塞,您的應用程式必須使用支援 原子鎖 的快取驅動程式。目前,這些快取驅動程式包括 memcacheddynamodbredismongodb(包含在官方 mongodb/laravel-mongodb 套件中)、databasefilearray 驅動程式。此外,您不能使用 cookie Session 驅動程式。

預設情況下,Laravel 允許使用相同 Session 的請求同時執行。因此,例如,如果您使用 JavaScript HTTP 函式庫向應用程式發出兩個 HTTP 請求,它們將同時執行。對於許多應用程式來說,這不是問題;但是,在少數向兩個不同的應用程式端點發出並發請求且都寫入 Session 資料的應用程式中,可能會發生 Session 資料遺失。

為了緩解這種情況,Laravel 提供了允許您限制給定 Session 的並發請求的功能。首先,您只需將 block 方法鏈接到路由定義上。在此範例中,對 /profile 端點的傳入請求將獲取 Session 鎖。當此鎖被持有時,任何對共用相同 Session ID 的 /profile/order 端點的傳入請求將等待第一個請求完成執行後再繼續執行:

Route::post('/profile', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10);

Route::post('/order', function () {
    // ...
})->block($lockSeconds = 10, $waitSeconds = 10);

block 方法接受兩個可選參數。block 方法接受的第一個參數是 Session 鎖應該持有的最大秒數,然後才會被釋放。當然,如果請求在此時間之前完成執行,鎖將會更早釋放。

block 方法接受的第二個參數是請求在嘗試獲取 Session 鎖時應該等待的秒數。如果請求無法在給定的秒數內獲取 Session 鎖,將拋出 Illuminate\Contracts\Cache\LockTimeoutException

如果這兩個參數都沒有傳遞,鎖將被獲取最多 10 秒,請求將在嘗試獲取鎖時最多等待 10 秒:

Route::post('/profile', function () {
    // ...
})->block();

新增自訂 Session 驅動程式 (Adding Custom Session Drivers)

實作驅動程式 (Implementing The Driver)

如果現有的 Session 驅動程式都不符合您的應用程式需求,Laravel 使您可以編寫自己的 Session 處理程式。您的自訂 Session 驅動程式應實作 PHP 內建的 SessionHandlerInterface。此介面僅包含幾個簡單的方法。一個存根的 MongoDB 實作看起來像這樣:

<?php

namespace App\Extensions;

class MongoSessionHandler implements \SessionHandlerInterface
{
    public function open($savePath, $sessionName) {}
    public function close() {}
    public function read($sessionId) {}
    public function write($sessionId, $data) {}
    public function destroy($sessionId) {}
    public function gc($lifetime) {}
}

由於 Laravel 不包含用於容納擴充功能的預設目錄,您可以自由地將它們放置在任何您喜歡的地方。在此範例中,我們建立了一個 Extensions 目錄來容納 MongoSessionHandler

由於這些方法的目的不容易理解,以下是每個方法目的的概述:

  • open 方法通常用於基於檔案的 Session 儲存系統。由於 Laravel 附帶了 file Session 驅動程式,您很少需要在此方法中放置任何內容。您可以簡單地將此方法留空。
  • close 方法,與 open 方法一樣,通常也可以忽略。對於大多數驅動程式,它不是必需的。
  • read 方法應返回與給定 $sessionId 關聯的 Session 資料的字串版本。在驅動程式中取得或儲存 Session 資料時,無需進行任何序列化或其他編碼,因為 Laravel 將為您執行序列化。
  • write 方法應將與 $sessionId 關聯的給定 $data 字串寫入某些持久性儲存系統,例如 MongoDB 或您選擇的其他儲存系統。同樣,您不應執行任何序列化 - Laravel 將已經為您處理了。
  • destroy 方法應從持久性儲存中移除與 $sessionId 關聯的資料。
  • gc 方法應銷毀所有比給定 $lifetime(這是一個 UNIX 時間戳記)舊的 Session 資料。對於像 Memcached 和 Redis 這樣的自行過期系統,此方法可以留空。

註冊驅動程式 (Registering The Driver)

一旦您的驅動程式實作完成,您就可以將其註冊到 Laravel。要向 Laravel 的 Session 後端新增額外的驅動程式,您可以使用 Session Facade 提供的 extend 方法。您應該從 Service Providerboot 方法呼叫 extend 方法。您可以從現有的 App\Providers\AppServiceProvider 執行此操作,或建立一個全新的 Provider:

<?php

namespace App\Providers;

use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        // ...
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Session::extend('mongo', function (Application $app) {
            // Return an implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}

一旦 Session 驅動程式註冊完成,您可以使用 SESSION_DRIVER 環境變數或在應用程式的 config/session.php 設定檔中將 mongo 驅動程式指定為應用程式的 Session 驅動程式。