由於 HTTP 驅動的應用程序是無狀態的,Session 提供了一種在多個請求之間存儲有關用戶信息的方法,這類信息一般都存儲在後續請求可以訪問的持久存儲 / 後端中。
Laravel 通過同一個可讀性強的 API 處理各種自帶的後台驅動程序。支持諸如比較熱門的Memcached、 Redis和數據庫。
Session 的配置文件存儲在config/session.php
文件中。請務必查看此文件中對於你而言可用的選項。默認情況下,Laravel 為絕大多數應用程序配置的 Session 驅動為file
驅動,它適用於大多數程序。如果你的應用程序需要在多個 Web 服務器之間進行負載平衡,你應該選擇一個所有服務器都可以訪問的集中式存儲,例如 Redis 或數據庫。
Sessiondriver
的配置預設了每個請求存儲 Session 數據的位置。Laravel 自帶了幾個不錯而且開箱即用的驅動:
file
- Sessions 存儲在storage/framework/sessions
。cookie
- Sessions 被存儲在安全加密的 cookie 中。database
- Sessions 被存儲在關系型數據庫中。memcached
/ redis
- Sessions 被存儲在基於高速緩存的存儲系統中。dynamodb
- Sessions 被存儲在 AWS DynamoDB 中。array
- Sessions 存儲在 PHP 數組中,但不會被持久化。技巧 數組驅動一般用於測試並且防止存儲在 Session 中的數據被持久化。
使用database
Session 驅動時,你需要創建一個記錄 Session 的表。下面是Schema
的聲明示例:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
});
你可以使用 Artisan 命令session:table
生成這個遷移。了解更多數據庫遷移,請查看完整的文檔遷移文檔:
php artisan session:table
php artisan migrate
在 Laravel 使用 Redis Session 驅動前,你需要安裝 PhpRedis PHP 擴展,可以通過 PECL 或者 通過 Composer 安裝這個predis/predis
包 (~1.0)。更多關於 Redis 配置信息,查詢 Laravel 的 Redis 文檔.
技巧 在
session
配置文件里,connection
選項可以用來設置 Session 使用 Redis 連接方式。
在 Laravel 中有兩種基本的 Session 使用方式:全局session
助手函數和通過Request
實例。首先看下通過Request
實例訪問 Session , 它可以隱式綁定路由閉包或者控制器方法。記住,Laravel 會自動注入控制器方法的依賴。服務容器:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* 顯示指定用戶個人資料。
*/
public function show(Request $request, string $id): View
{
$value = $request->session()->get('key');
// ...
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
當你從 Session 獲取數據時,你也可以在get
方法第二個參數里傳遞一個 default 默認值,如果 Session 里不存在鍵值對 key 的數據結果,這個默認值就會返回。如果你傳遞給get
方法一個閉包作為默認值,這個閉包會被執行並且返回結果:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
你也可以在 Session 里使用 PHP 全局session
函數獲取和儲存數據。當這個session
函數以一個單獨的字符串形式被調用時,它將會返回這個 Session 鍵值對的結果。當函數以 key / value 數組形式被調用時,這些值會被存儲在 Session 里:
Route::get('/home', function () {
// 從 Session 獲取數據 ...
$value = session('key');
// 設置默認值...
$value = session('key', 'default');
// 在Session 里存儲一段數據 ...
session(['key' => 'value']);
});
技巧 通過 HTTP 請求實例與通過
session
助手函數方式使用 Session 之間沒有實際區別。兩種方式都是可的測試,你所有的測試用例中都可以通過assertSessionHas
方法進行斷言。
如果你想要從 Session 里獲取所有數據,你可以使用all
方法:
$data = $request->session()->all();
判斷 Session 里是否存在一個條目,你可以使用has
方法。如果條目存在has
,方法返回true
不存在則返回null
:
if ($request->session()->has('users')) {
// ...
}
判斷 Session 里是否存在一個即使結果值為null
的條目,你可以使用exists
方法:
if ($request->session()->exists('users')) {
// ...
}
要確定某個條目是否在會話中不存在,你可以使用 missing
方法。如果條目不存在,missing
方法返回true
:
if ($request->session()->missing('users')) {
// ...
}
Session 里存儲數據,你通常將使用 Request 實例中的put
方法或者session
助手函數:
// 通過 Request 實例存儲 ...
$request->session()->put('key', 'value');
// 通過全局 Session 助手函數存儲 ...
session(['key' => 'value']);
push
方法可以把一個新值推入到以數組形式存儲的 session 值里。例如:如果user.teams
鍵值對有一個關於團隊名字的數組,你可以推入一個新值到這個數組里:
$request->session()->push('user.teams', 'developers');
pull
方法會從 Session 里獲取並且刪除一個條目,只需要一步如下:
$value = $request->session()->pull('key', 'default');
如果你的 Session 數據里有整形你希望進行加減操作,可以使用increment
和decrement
方法:
$request->session()->increment('count');
$request->session()->increment('count', $incrementBy = 2);
$request->session()->decrement('count');
$request->session()->decrement('count', $decrementBy = 2);
有時你可能想在 Session 里為下次請求存儲一些條目。你可以使用flash
方法。使用這個方法,存儲在 Session 的數據將立即可用並且會保留到下一個 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
方法會從 Session 刪除一些數據。如果你想刪除所有 Session 數據,可以使用flush
方法:
// 刪除一個單獨的鍵值對 ...
$request->session()->forget('name');
// 刪除多個 鍵值對 ...
$request->session()->forget(['name', 'status']);
$request->session()->flush();
重新生成 Session ID 經常被用來阻止惡意用戶使用 session fixation 攻擊你的應用。
如果你正在使用入門套件或 Laravel Fortify中的任意一種, Laravel 會在認證階段自動生成 Session ID;然而如果你需要手動重新生成 Session ID ,可以使用regenerate
方法:
$request->session()->regenerate();
如果你需要重新生成 Session ID 並同時刪除所有 Session 里的數據,可以使用invalidate
方法:
$request->session()->invalidate();
注意 應用 Session 阻塞功能,你的應用必須使用一個支持原子鎖 的緩存驅動。目前,可用的緩存驅動有
memcached
、dynamodb
、redis
和database
等。另外,你可能不會使用cookie
Session 驅動。
默認情況下,Laravel 允許使用同一 Session 的請求並發地執行,舉例來說,如果你使用一個 JavaScript HTTP 庫向你的應用執行兩次 HTTP 請求,它們將同時執行。對多數應用這不是問題,然而 在一小部分應用中可能出現 Session 數據丟失,這些應用會向兩個不同的應用端並發請求,並同時寫入數據到 Session。
為了解決這個問題,Laravel 允許你限制指定 Session 的並發請求。首先,你可以在路由定義時使用block
鏈式方法。在這個示例中,一個到/profile
的路由請求會拿到一把 Session 鎖。當它處在鎖定狀態時,任何使用相同 Session ID 的到/profile
或/order
的路由請求都必須等待,直到第一個請求處理完成後再繼續執行:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
block
方法接受兩個可選參數。block
方法接受的第一個參數是 Session 鎖釋放前應該持有的最大秒數。當然,如果請求在此時間之前完成執行,鎖將提前釋放。
block
方法接受的第二個參數是請求在試圖獲得 Session 鎖時應該等待的秒數。如果請求在給定的秒數內無法獲得會話鎖,將拋出Illuminate\Contracts\Cache\LockTimeoutException
異常。
如果不傳參,那麽 Session 鎖默認鎖定最大時間是 10 秒,請求鎖最大的等待時間也是 10 秒:
Route::post('/profile', function () {
// ...
})->block()
如果現存的 Session 驅動不能滿足你的需求,Laravel 允許你自定義 Session Handler。你的自定義驅動應實現 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
Facade 提供的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
{
/**
* 注冊任意應用服務。
*/
public function register(): void
{
// ...
}
/**
* 啟動任意應用服務。
*/
public function boot(): void
{
Session::extend('mongo', function (Application $app) {
// 返回一個 SessionHandlerInterface 接口的實現 ...
return new MongoSessionHandler;
});
}
}
一旦 Session 驅動注冊完成,就可以在config/session.php
配置文件選擇使用mongo
驅動。