HTTP 會話
簡介
由於 HTTP 驅動的應用程式是無狀態的,因此會話提供了一種在多個請求中儲存使用者資訊的方法。該使用者資訊通常放置在持久儲存區/後端中,可以從後續請求存取。
Laravel 隨附各種會話後端,這些後端透過具表現力、統一的 API 存取。支援熱門的後端,例如 Memcached、Redis 和資料庫。
設定
應用程式的會話設定檔儲存在 config/session.php
。請務必查看此檔案中可用的選項。預設情況下,Laravel 設定為使用 database
會話驅動程式。
會話的 driver
設定選項定義了每個請求的會話資料將儲存在哪裡。Laravel 包含各種驅動程式
-
file
- 會話儲存在storage/framework/sessions
中。 -
cookie
- 會話儲存在安全、加密的 Cookie 中。 -
database
- 會話儲存在關聯式資料庫中。 -
memcached
/redis
- 會話儲存在其中一個快速、基於快取的儲存區中。 -
dynamodb
- 會話儲存在 AWS DynamoDB 中。 -
array
- 會話儲存在 PHP 陣列中,不會被持久化。
陣列驅動程式主要在 測試 期間使用,並防止儲存在會話中的資料被持久化。
驅動程式先決條件
資料庫
使用 database
會話驅動程式時,您需要確保您有一個資料庫表來包含會話資料。通常,這包含在 Laravel 的預設 0001_01_01_000000_create_users_table.php
資料庫遷移中;但是,如果因為任何原因您沒有 sessions
表,您可以使用 make:session-table
Artisan 命令來產生此遷移
php artisan make:session-table php artisan migrate
Redis
在 Laravel 中使用 Redis 會話之前,您需要透過 PECL 安裝 PhpRedis PHP 擴充功能,或透過 Composer 安裝 predis/predis
套件 (~1.0)。如需設定 Redis 的詳細資訊,請參閱 Laravel 的 Redis 文件。
可以使用 SESSION_CONNECTION
環境變數或 session.php
設定檔中的 connection
選項來指定哪個 Redis 連線用於會話儲存。
與會話互動
擷取資料
在 Laravel 中使用會話資料主要有兩種方式:全域 session
輔助函數和透過 Request
執行個體。首先,讓我們看看如何透過 Request
執行個體存取會話,該執行個體可以在路由閉包或控制器方法上進行類型提示。請記住,控制器方法相依性會透過 Laravel 服務容器自動注入
<?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]); }}
當您從會話中擷取項目時,您也可以將預設值作為 get
方法的第二個引數傳遞。如果指定的索引鍵在會話中不存在,將會傳回此預設值。如果您將閉包作為預設值傳遞給 get
方法,且請求的索引鍵不存在,則會執行閉包並傳回其結果
$value = $request->session()->get('key', 'default'); $value = $request->session()->get('key', function () { return 'default';});
全域會話輔助函數
您也可以使用全域 session
PHP 函數來擷取和儲存會話中的資料。當使用單一字串引數呼叫 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']);});
透過 HTTP 請求執行個體使用會話與使用全域 session
輔助函數之間幾乎沒有實際差異。兩種方法都可以透過所有測試案例中可用的 assertSessionHas
方法進行測試。
擷取所有會話資料
如果您想要擷取會話中的所有資料,您可以使用 all
方法
$data = $request->session()->all();
擷取部分會話資料
可以使用 only
和 except
方法來擷取會話資料的子集
$data = $request->session()->only(['username', 'email']); $data = $request->session()->except(['username', 'email']);
判斷會話中是否存在項目
若要判斷項目是否存在於會話中,您可以使用 has
方法。如果項目存在且不是 null
,則 has
方法會傳回 true
if ($request->session()->has('users')) { // ...}
若要判斷項目是否存在於會話中,即使其值為 null
,您可以使用 exists
方法
if ($request->session()->exists('users')) { // ...}
若要判斷項目不存在於會話中,您可以使用 missing
方法。如果項目不存在,則 missing
方法會傳回 true
if ($request->session()->missing('users')) { // ...}
儲存資料
若要將資料儲存在會話中,您通常會使用請求執行個體的 put
方法或全域 session
輔助函數
// Via a request instance...$request->session()->put('key', 'value'); // Via the global "session" helper...session(['key' => 'value']);
推送到陣列會話值
push
方法可用於將新值推送到屬於陣列的會話值上。例如,如果 user.teams
索引鍵包含團隊名稱的陣列,您可以像這樣將新值推送到陣列上
$request->session()->push('user.teams', 'developers');
擷取和刪除項目
pull
方法會在單一陳述式中擷取並刪除會話中的項目
$value = $request->session()->pull('key', 'default');
遞增和遞減會話值
如果您的會話資料包含您想要遞增或遞減的整數,您可以使用 increment
和 decrement
方法
$request->session()->increment('count'); $request->session()->increment('count', $incrementBy = 2); $request->session()->decrement('count'); $request->session()->decrement('count', $decrementBy = 2);
快閃資料
有時您可能希望在會話中儲存項目以用於下一個請求。您可以使用 flash
方法來做到這一點。使用此方法儲存在會話中的資料將會立即且在後續的 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!');
刪除資料
forget
方法會從會話中移除一段資料。如果您想要從會話中移除所有資料,您可以使用 flush
方法
// Forget a single key...$request->session()->forget('name'); // Forget multiple keys...$request->session()->forget(['name', 'status']); $request->session()->flush();
重新產生會話 ID
重新產生 Session ID 通常是為了防止惡意使用者利用Session 固定攻擊來攻擊您的應用程式。
如果您使用 Laravel 的應用程式啟動套件或 Laravel Fortify,Laravel 會在驗證期間自動重新產生 Session ID;但是,如果您需要手動重新產生 Session ID,可以使用 regenerate
方法。
$request->session()->regenerate();
如果您需要重新產生 Session ID 並在單一語句中移除 Session 中的所有資料,可以使用 invalidate
方法。
$request->session()->invalidate();
會話封鎖
若要使用 Session 封鎖,您的應用程式必須使用支援原子鎖的快取驅動程式。目前,這些快取驅動程式包括 memcached
、dynamodb
、redis
、mongodb
(包含在官方的 mongodb/laravel-mongodb
套件中)、database
、file
和 array
驅動程式。此外,您不得使用 cookie
Session 驅動程式。
預設情況下,Laravel 允許使用相同 Session 的請求同時執行。舉例來說,如果您使用 JavaScript HTTP 函式庫向您的應用程式發出兩個 HTTP 請求,它們將會同時執行。對於許多應用程式來說,這不是問題;但是,在少數同時向兩個不同的應用程式端點發出請求,且這兩個端點都會寫入 Session 資料的應用程式中,可能會發生 Session 資料遺失的情況。
為了減輕此問題,Laravel 提供了功能,讓您可以限制特定 Session 的並行請求。若要開始使用,您只需將 block
方法串連到您的路由定義上。在此範例中,傳入 /profile
端點的請求將會取得 Session 鎖定。在持有此鎖定期間,任何傳入 /profile
或 /order
端點且共享相同 Session ID 的請求,都會等待第一個請求完成執行後再繼續執行。
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 驅動程式都不符合您的應用程式需求,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 這樣的自我過期系統,此方法可以留空。
註冊驅動程式
一旦您的驅動程式實作完成,您就可以將它註冊到 Laravel 中。若要將額外的驅動程式新增到 Laravel 的 Session 後端,您可以使用 Session
Facades 提供的 extend
方法。您應該從服務提供者的 boot
方法呼叫 extend
方法。您可以從現有的 App\Providers\AppServiceProvider
執行此操作,或建立一個全新的提供者。
<?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 驅動程式。