HTTP Session
簡介
由於 HTTP 驅動的應用程式是無狀態的,Session 提供了一種在多個請求之間儲存使用者資訊的方法。這些使用者資訊通常會被放置在持久化儲存/後端中,以便後續請求能夠訪問。
Laravel 附帶了多種 Session 後端,透過富有表現力且統一的 API 進行訪問。它支援諸如 Memcached、Redis 和資料庫等流行的後端。
配置
應用程式的 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-table2 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): View14 {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 資料
only 和 except 方法可用於獲取 Session 資料的一個子集。
1$data = $request->session()->only(['username', 'email']);2 3$data = $request->session()->except(['username', 'email']);
判斷 Session 中是否存在某項
要判斷某項是否存在於 Session 中,可以使用 has 方法。如果該項存在且不為 null,has 方法返回 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 資料包含一個需要遞增或遞減的整數,可以使用 increment 和 decrement 方法。
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 快取方法,如 get、put、remember、forget 等,但僅限於當前 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) 的快取驅動。目前,這些快取驅動包括 memcached、dynamodb、redis、mongodb(包含在官方的 mongodb/laravel-mongodb 包中)、database、file 和 array 驅動。此外,不能使用 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 附帶了fileSession 驅動,你幾乎不需要在此方法中填入任何內容。你可以簡單地將其留空。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 ServiceProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 // ...18 }19 20 /**21 * Bootstrap any application services.22 */23 public function boot(): void24 {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 驅動。