跳轉至內容

HTTP Session

簡介

由於 HTTP 驅動的應用程式是無狀態的,Session 提供了一種在多個請求之間儲存使用者資訊的方法。這些使用者資訊通常會被放置在持久化儲存/後端中,以便後續請求能夠訪問。

Laravel 附帶了多種 Session 後端,透過富有表現力且統一的 API 進行訪問。它支援諸如 MemcachedRedis 和資料庫等流行的後端。

配置

應用程式的 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 陣列中,不會被持久化。

array 驅動主要用於 測試,它會防止 Session 中儲存的資料被持久化。

驅動前提條件

資料庫

使用 database Session 驅動時,需要確保擁有一個用於存放 Session 資料的資料庫表。通常,這包含在 Laravel 預設的 0001_01_01_000000_create_users_table.php 資料庫遷移檔案中;然而,如果由於某種原因你沒有 sessions 表,可以使用 make:session-table Artisan 命令來生成該遷移。

1php artisan make:session-table
2 
3php artisan migrate

Redis

在使用 Redis Session 之前,需要透過 PECL 安裝 PhpRedis PHP 擴充套件,或者透過 Composer 安裝 predis/predis 包 (~1.0)。有關配置 Redis 的更多資訊,請查閱 Laravel 的 Redis 文件

可以使用 SESSION_CONNECTION 環境變數,或 session.php 配置檔案中的 connection 選項來指定用於 Session 儲存的 Redis 連線。

與 Session 互動

獲取資料

在 Laravel 中使用 Session 資料主要有兩種方式:全域性 session 輔助函式和透過 Request 例項。首先,讓我們看看如何透過 Request 例項訪問 Session,它可以被型別提示注入到路由閉包或控制器方法中。請記住,控制器方法依賴項會自動透過 Laravel 服務容器進行注入。

1<?php
2 
3namespace App\Http\Controllers;
4 
5use Illuminate\Http\Request;
6use Illuminate\View\View;
7 
8class UserController extends Controller
9{
10 /**
11 * Show the profile for the given user.
12 */
13 public function show(Request $request, string $id): View
14 {
15 $value = $request->session()->get('key');
16 
17 // ...
18 
19 $user = $this->users->find($id);
20 
21 return view('user.profile', ['user' => $user]);
22 }
23}

當從 Session 中檢索項時,可以將預設值作為 get 方法的第二個引數傳遞。如果指定的鍵在 Session 中不存在,將返回此預設值。如果你傳遞一個閉包作為 get 方法的預設值,且請求的鍵不存在,該閉包將被執行並返回其結果。

1$value = $request->session()->get('key', 'default');
2 
3$value = $request->session()->get('key', function () {
4 return 'default';
5});

全域性 Session 輔助函式

你還可以使用全域性 session PHP 函式來檢索和儲存 Session 資料。當使用單個字串引數呼叫 session 輔助函式時,它將返回該 Session 鍵的值。當使用鍵值對陣列呼叫輔助函式時,這些值將被儲存在 Session 中。

1Route::get('/home', function () {
2 // Retrieve a piece of data from the session...
3 $value = session('key');
4 
5 // Specifying a default value...
6 $value = session('key', 'default');
7 
8 // Store a piece of data in the session...
9 session(['key' => 'value']);
10});

使用 HTTP 請求例項訪問 Session 與使用全域性 session 輔助函式之間幾乎沒有實際區別。這兩種方法都可以透過在所有測試用例中可用的 assertSessionHas 方法進行 測試

獲取所有 Session 資料

如果你想獲取 Session 中的所有資料,可以使用 all 方法。

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

獲取部分 Session 資料

onlyexcept 方法可用於獲取 Session 資料的一個子集。

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

判斷 Session 中是否存在某項

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

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

即使某項的值為 null,若要判斷其是否存在於 Session 中,可以使用 exists 方法。

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

若要判斷某項不存在於 Session 中,可以使用 missing 方法。如果該項不存在,missing 方法返回 true

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

儲存資料

要將資料儲存到 Session 中,通常使用請求例項的 put 方法或全域性 session 輔助函式。

1// Via a request instance...
2$request->session()->put('key', 'value');
3 
4// Via the global "session" helper...
5session(['key' => 'value']);

向陣列 Session 值中追加資料

push 方法可用於將一個新值推入陣列形式的 Session 值中。例如,如果 user.teams 鍵包含一個團隊名稱陣列,你可以像這樣將新值推入該陣列。

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

檢索並刪除某項

pull 方法可以在單條語句中檢索並刪除 Session 中的某項。

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

遞增和遞減 Session 值

如果你的 Session 資料包含一個需要遞增或遞減的整數,可以使用 incrementdecrement 方法。

1$request->session()->increment('count');
2 
3$request->session()->increment('count', $incrementBy = 2);
4 
5$request->session()->decrement('count');
6 
7$request->session()->decrement('count', $decrementBy = 2);

快閃記憶體資料 (Flash Data)

有時你可能希望在下一次請求中臨時儲存一些資料,可以使用 flash 方法。使用此方法儲存的資料在當前請求和隨後的 HTTP 請求中可用。隨後的 HTTP 請求結束後,快閃記憶體資料將被刪除。快閃記憶體資料主要用於短期狀態訊息。

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

如果你需要將快閃記憶體資料保留幾個請求,可以使用 reflash 方法,它會將所有快閃記憶體資料保留額外的請求。如果你只需要保留特定的快閃記憶體資料,可以使用 keep 方法。

1$request->session()->reflash();
2 
3$request->session()->keep(['username', 'email']);

若要僅在當前請求中持久化快閃記憶體資料,可以使用 now 方法。

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

刪除資料

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

1// Forget a single key...
2$request->session()->forget('name');
3 
4// Forget multiple keys...
5$request->session()->forget(['name', 'status']);
6 
7$request->session()->flush();

重新生成 Session ID

重新生成 Session ID 通常是為了防止惡意使用者利用應用程式的 會話固定 (Session fixation) 攻擊。

如果你使用的是 Laravel 入門套件Laravel Fortify,Laravel 會在認證期間自動重新生成 Session ID;但如果你需要手動重新生成 Session ID,可以使用 regenerate 方法。

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

如果你需要在單條語句中重新生成 Session ID 並清除 Session 中的所有資料,可以使用 invalidate 方法。

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

Session 快取

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

Session 快取非常適合儲存臨時的、特定於使用者的資料,這些資料需要在同一 Session 的多個請求之間保留,但無需永久儲存。這包括表單資料、臨時計算結果、API 響應或任何其他應繫結到特定使用者 Session 的瞬態資料。

你可以透過 Session 上的 cache 方法訪問 Session 快取。

1$discount = $request->session()->cache()->get('discount');
2 
3$request->session()->cache()->put(
4 'discount', 10, now()->plus(minutes: 5)
5);

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

Session 阻塞

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

預設情況下,Laravel 允許使用同一 Session 的請求併發執行。例如,如果你使用 JavaScript HTTP 庫嚮應用程式傳送兩個 HTTP 請求,它們會同時執行。對於許多應用程式來說,這不是問題;但在少數應用程式中,如果併發請求同時寫入 Session,可能會導致 Session 資料丟失。

為了緩解這個問題,Laravel 提供了限制給定 Session 併發請求的功能。要開始使用,只需在路由定義中鏈式呼叫 block 方法。在此示例中,對 /profile 終結點的傳入請求將獲取一個 Session 鎖。持有此鎖期間,任何共享相同 Session ID 且訪問 /profile/order 終結點的請求都將等待第一個請求執行完畢後再繼續執行。

1Route::post('/profile', function () {
2 // ...
3})->block($lockSeconds = 10, $waitSeconds = 10);
4 
5Route::post('/order', function () {
6 // ...
7})->block($lockSeconds = 10, $waitSeconds = 10);

block 方法接受兩個可選引數。第一個引數是 Session 鎖在釋放前應保持的最長秒數。當然,如果請求在此時間內執行完畢,鎖會提前釋放。

block 方法接受的第二個引數是請求在嘗試獲取 Session 鎖時應等待的秒數。如果請求無法在給定時間內獲取到 Session 鎖,將丟擲 Illuminate\Contracts\Cache\LockTimeoutException 異常。

如果未傳遞這些引數,鎖最多持有 10 秒,請求在嘗試獲取鎖時最多等待 10 秒。

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

新增自定義 Session 驅動

實現驅動

如果現有的 Session 驅動均無法滿足你的應用程式需求,Laravel 允許編寫自定義的 Session 處理程式。自定義 Session 驅動應實現 PHP 內建的 SessionHandlerInterface。該介面僅包含幾個簡單的方法。以下是一個 MongoDB 實現的存根示例:

1<?php
2 
3namespace App\Extensions;
4 
5class MongoSessionHandler implements \SessionHandlerInterface
6{
7 public function open($savePath, $sessionName) {}
8 public function close() {}
9 public function read($sessionId) {}
10 public function write($sessionId, $data) {}
11 public function destroy($sessionId) {}
12 public function gc($lifetime) {}
13}

由於 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 等支援自過期的系統,此方法可以留空。

註冊驅動

驅動實現完成後,即可將其註冊到 Laravel。要向 Laravel 的 Session 後端新增額外驅動,可以使用 Session 門面 (Facade) 提供的 extend 方法。你應該在 服務提供者boot 方法中呼叫 extend 方法。你可以將其放在現有的 App\Providers\AppServiceProvider 中,或建立一個全新的提供者。

1<?php
2 
3namespace App\Providers;
4 
5use App\Extensions\MongoSessionHandler;
6use Illuminate\Contracts\Foundation\Application;
7use Illuminate\Support\Facades\Session;
8use Illuminate\Support\ServiceProvider;
9 
10class SessionServiceProvider extends ServiceProvider
11{
12 /**
13 * Register any application services.
14 */
15 public function register(): void
16 {
17 // ...
18 }
19 
20 /**
21 * Bootstrap any application services.
22 */
23 public function boot(): void
24 {
25 Session::extend('mongo', function (Application $app) {
26 // Return an implementation of SessionHandlerInterface...
27 return new MongoSessionHandler;
28 });
29 }
30}

註冊完成後,你可以在 SESSION_DRIVER 環境變數中或應用程式的 config/session.php 配置檔案中,將 mongo 指定為應用程式的 Session 驅動。