跳至內容

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 陣列中,不會被持久化。
lightbulb

陣列驅動程式主要在 測試 期間使用,並防止儲存在會話中的資料被持久化。

驅動程式先決條件

資料庫

使用 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 文件

lightbulb

可以使用 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']);
});
lightbulb

透過 HTTP 請求執行個體使用會話與使用全域 session 輔助函數之間幾乎沒有實際差異。兩種方法都可以透過所有測試案例中可用的 assertSessionHas 方法進行測試

擷取所有會話資料

如果您想要擷取會話中的所有資料,您可以使用 all 方法

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

擷取部分會話資料

可以使用 onlyexcept 方法來擷取會話資料的子集

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

遞增和遞減會話值

如果您的會話資料包含您想要遞增或遞減的整數,您可以使用 incrementdecrement 方法

$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();

會話封鎖

exclamation

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