跳到內容

HTTP 會話

簡介

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

Laravel 附帶了各種會話後端,這些後端通過富有表現力、統一的 API 進行存取。支援流行的後端,例如 MemcachedRedis 和資料庫。

設定

您的應用程式的會話設定檔儲存在 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-table
2 
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): 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}

當您從會話中檢索項目時,您也可以將預設值作為第二個參數傳遞給 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();

檢索部分會話資料

onlyexcept 方法可用於檢索會話資料的子集

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');

遞增和遞減會話值

如果您的會話資料包含您希望遞增或遞減的整數,您可以使用 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 方法執行此操作。使用此方法儲存在會話中的資料將立即在隨後的 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();

會話封鎖

要利用會話封鎖,您的應用程式必須使用支援 原子鎖 的快取驅動程式。目前,這些快取驅動程式包括 memcacheddynamodbredismongodb(包含在官方 mongodb/laravel-mongodb 套件中)、databasefilearray 驅動程式。此外,您不得使用 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 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 驅動程式指定為應用程式的會話驅動程式。