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 命令來產生此遷移
1php artisan make:session-table2 3php 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 服務容器 自動注入
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}
當您從會話中檢索項目時,您也可以將預設值作為第二個參數傳遞給 get
方法。如果指定的鍵在會話中不存在,將返回此預設值。如果您將閉包作為預設值傳遞給 get
方法,並且請求的鍵不存在,則將執行閉包並返回其結果
1$value = $request->session()->get('key', 'default');2 3$value = $request->session()->get('key', function () {4 return 'default';5});
全域會話輔助函式
您也可以使用全域 session
PHP 函式來檢索和儲存會話中的資料。當使用單個字串參數調用 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
輔助函式之間幾乎沒有實際區別。這兩種方法都可以通過 assertSessionHas
方法進行 測試,該方法在您的所有測試案例中都可用。
檢索所有會話資料
如果您想檢索會話中的所有資料,可以使用 all
方法
1$data = $request->session()->all();
檢索部分會話資料
only
和 except
方法可用於檢索會話資料的子集
1$data = $request->session()->only(['username', 'email']);2 3$data = $request->session()->except(['username', 'email']);
判斷項目是否存在於會話中
要判斷項目是否存在於會話中,可以使用 has
方法。如果項目存在且不為 null
,則 has
方法返回 true
1if ($request->session()->has('users')) {2 // ...3}
要判斷項目是否存在於會話中,即使其值為 null
,也可以使用 exists
方法
1if ($request->session()->exists('users')) {2 // ...3}
要判斷項目是否不存在於會話中,可以使用 missing
方法。如果項目不存在,則 missing
方法返回 true
1if ($request->session()->missing('users')) {2 // ...3}
儲存資料
要在會話中儲存資料,您通常會使用請求實例的 put
方法或全域 session
輔助函式
1// Via a request instance...2$request->session()->put('key', 'value');3 4// Via the global "session" helper...5session(['key' => 'value']);
推送到陣列會話值
push
方法可用於將新值推送到作為陣列的會話值。例如,如果 user.teams
鍵包含團隊名稱陣列,您可以像這樣將新值推送到陣列中
1$request->session()->push('user.teams', 'developers');
檢索和刪除項目
pull
方法將在單個語句中從會話中檢索和刪除項目
1$value = $request->session()->pull('key', 'default');
遞增和遞減會話值
如果您的會話資料包含您希望遞增或遞減的整數,您可以使用 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
方法執行此操作。使用此方法儲存在會話中的資料將立即在隨後的 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
方法將從會話中刪除一條資料。如果您想從會話中刪除所有資料,可以使用 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();
重新產生會話 ID
重新產生會話 ID 通常是為了防止惡意使用者利用應用程式上的 會話固定 攻擊。
如果您正在使用 Laravel 應用程式入門套件 或 Laravel Fortify 之一,Laravel 會在身份驗證期間自動重新產生會話 ID;但是,如果您需要手動重新產生會話 ID,可以使用 regenerate
方法
1$request->session()->regenerate();
如果您需要重新產生會話 ID 並在單個語句中從會話中刪除所有資料,可以使用 invalidate
方法
1$request->session()->invalidate();
會話封鎖
要利用會話封鎖,您的應用程式必須使用支援 原子鎖 的快取驅動程式。目前,這些快取驅動程式包括 memcached
、dynamodb
、redis
、mongodb
(包含在官方 mongodb/laravel-mongodb
套件中)、database
、file
和 array
驅動程式。此外,您不得使用 cookie
會話驅動程式。
預設情況下,Laravel 允許使用相同會話的請求同時執行。因此,例如,如果您使用 JavaScript HTTP 庫向您的應用程式發出兩個 HTTP 請求,它們將同時執行。對於許多應用程式來說,這不是問題;但是,在少數應用程式中可能會發生會話資料丟失,這些應用程式向兩個不同的應用程式端點發出並行請求,這兩個端點都將資料寫入會話。
為了減輕這種情況,Laravel 提供了允許您限制給定會話的並行請求的功能。要開始使用,您可以簡單地將 block
方法鏈接到您的路由定義。在此範例中,對 /profile
端點的傳入請求將獲取會話鎖。在持有此鎖的同時,對 /profile
或 /order
端點的任何傳入請求(它們共享相同的會話 ID)都將等待第一個請求完成執行,然後再繼續執行
1Route::post('/profile', function () {2 // ...3})->block($lockSeconds = 10, $waitSeconds = 10);4 5Route::post('/order', function () {6 // ...7})->block($lockSeconds = 10, $waitSeconds = 10);
block
方法接受兩個可選參數。block
方法接受的第一個參數是會話鎖應保持的最大秒數,然後才會釋放。當然,如果請求在此時間之前完成執行,則鎖將更早釋放。
block
方法接受的第二個參數是請求在嘗試獲取會話鎖時應等待的秒數。如果請求無法在給定的秒數內獲取會話鎖,則會拋出 Illuminate\Contracts\Cache\LockTimeoutException
。
如果未傳遞這兩個參數,則鎖將最多獲取 10 秒,並且請求在嘗試獲取鎖時最多等待 10 秒
1Route::post('/profile', function () {2 // ...3})->block();
新增自訂會話驅動程式
實作驅動程式
如果現有的會話驅動程式都不符合您的應用程式需求,Laravel 允許您編寫自己的會話處理程式。您的自訂會話驅動程式應實作 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
方法通常用於基於檔案的會話儲存系統。由於 Laravel 附帶了file
會話驅動程式,因此您很少需要在此方法中放置任何內容。您可以簡單地將此方法留空。- 與
open
方法一樣,close
方法通常也可以忽略。對於大多數驅動程式,它不是必需的。 read
方法應返回與給定的$sessionId
關聯的會話資料的字串版本。在驅動程式中檢索或儲存會話資料時,無需進行任何序列化或其他編碼,因為 Laravel 將為您執行序列化。write
方法應將與$sessionId
關聯的給定$data
字串寫入某些持久性儲存系統,例如 MongoDB 或您選擇的其他儲存系統。同樣,您不應執行任何序列化 - Laravel 將已為您處理了。destroy
方法應從持久性儲存中刪除與$sessionId
關聯的資料。gc
方法應銷毀所有早於給定$lifetime
的會話資料,$lifetime
是 UNIX 時間戳記。對於像 Memcached 和 Redis 這樣的自到期系統,此方法可以留空。
註冊驅動程式
一旦您的驅動程式已實作,您就可以將其註冊到 Laravel。要將其他驅動程式新增到 Laravel 的會話後端,您可以使用 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
驅動程式指定為應用程式的會話驅動程式。