15分鐘搞懂Laravel的Session機制

由於 HTTP 驅動的應用是無狀態的,所以被戲稱為剔牙老奶奶,Session 提供了一種在多個請求之間儲存有關用戶資料的方法。使用者資料一般被放在持久保存或後端以便供後續的請求取用
Laravel 通過同一個可讀性強的 API 處理各種自帶的後台驅動程序以支持諸如比較熱門的 Memcached, Redis 和資料庫
設定
Session 的設定檔案儲存在 config/session.php 檔案中。請務必查看此檔案,看看有哪些可能可用的選項。預設情況下,Laravel 為絕大多數應用配置的 Session 驅動為 file ,並在大多數應用裏頭都運作良好。在生產環境中,你可以考慮使用 memcached 或 redis 驅動,讓 Session 的性能更加出色,如果預算有限的情況下,使用資料庫也是選項之一。假如你的應用透過多個網頁伺服器來進行負載平衡,你需要選擇一個作為核心來進行儲存已讓其他服務器可以存取,諸如 Redis 或 資料庫
這個 Session 驅動設定選項定義了每一個請求的 Session 資料將要存在哪裡,Laravel內建了很多好用的驅動給你選擇
驅動 | 說明 |
---|---|
file | 將 Sessions 存在本地端的 storage/framework/sessions 資料夾 |
cookie | Sessions 被存在安全且加密的 Cookies |
database | Sessions 被存在關聯資料庫 |
memcached / redis | Sessions 被存在幾乎是最快的,透過快取來儲存 |
dynamodb | Sessions 被存在 AWS DynamoDB |
array | Sessions 被存在 PHP 陣列且無法持久保存 |
array 驅動主要是用來進行測試,以避免資料存在Session之後會被持久保存
驅動程式先決條件
資料庫
使用 database 作為 Session 驅動時,你需要創建一張包含 Session 各個數據的表格。以下是使用 Schema 建表的例子:
Schema::create('sessions', function ($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 來生成此遷移檔案。如需了解更多的資料庫遷移檔案,你可以查看 Database Migration 的章節
php artisan session:table
php artisan migrate
Redis
Laravel 在使用 Redis 作為 Session 驅動之前,需要安裝 Redis 的 PHP 擴展或者通過 Composer 安裝 predis/predis 擴展包 (~1.0)。Redis 的詳細設定資訊參考 Laravel Redis 文件
技巧:
在 session 配置檔案中,connection 選項可用於指定會話使用哪個 Redis 連接
使用 Session
取用資料
Laravel 中處理 Session 資料有兩種主要方法:全局幫助函式 session() 和通過一個 Request 實例。首先,我們來看看通過控制器方法型別提示一個 Request 實例來訪問 Session。控制器方法依賴項會透過 Laravel 服務容器來實現自動注入
//app\Http\Controllers\UserController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* @param Request $request
* @param int $id
* @return Response
*/
public function show(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
}
當你從 Session 獲取值時,你還可以傳遞一個預設值作為 get() 的第二個參數。如果 Session 中不存在指定的鍵,便會回傳這個預設值。若傳遞一個 Closure 作為 get() 方法的預設值,並且所請求的鍵並不存在時, get() 將執行 Closure 並回傳其結果:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
全域 Session 幫助函式
你也可以使用全域的 PHP 幫助函式 session() 來獲取和儲存 Session 資料。 使用單個字串類型的值作為參數呼叫幫助函式 session() 時,它會回傳該字串對應的 Session 鍵的值。當使用一個鍵值對陣列作為參數呼叫幫助函式 session() 時,傳入的鍵值將會儲存在 Session 中
//routes\web.php
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 與使用全域幫助函式 session() 兩者之間並沒有實質上的區別。這兩種方法都可以通過所有測試用例中可用的 assertSessionHas() 來進行測試
取得所有 Session 資料
如果你想獲取 session 中的所有資料,可以使用 all()
$data = $request->session()->all();
確認是否某個值存在於 Session
要確定 Session 中是否存在某個值,可以使用 has()。如果該值存在且不為 null,那麼 has() 會回傳 true:
if ($request->session()->has('users')) {
//
}
要確定 Session 中是否存在某個值,即使其值為 null,也可以使用 exists() 。如果值存在,則 exists() 返回 true:
if ($request->session()->exists('users')) {
//
}
has() 和 exists() 功能類似,新手容易混淆,先記得 has() 最嚴格即可
儲存資料
想要儲存資料到 Session,你可以使用 put() ,或者使用幫助函式 session()
// 透過請求實例...
$request->session()->put('key', 'value');
// 透過全域幫助函式...
session(['key' => 'value']);
保存資料到 Session 陣列中
push() 可以將一個新的值添加到 Session 陣列內。例如假設 user.teams 這個鍵是包括團隊名稱的陣列,你可以這樣將一個新的值加入到陣列中:
$request->session()->push('user.teams', 'developers');
取得並刪除一筆資料
pull() 可以只使用一條程式碼就從 Session 中取用資料並刪除該資料:
$value = $request->session()->pull('key', 'default');
增加或減少 Session 裏頭的值
假如你的 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 ID 通常是為了防止惡意用戶利用"Session 固定"對你的應用進行攻擊
如果你使用的是內建的 LoginController,Laravel 會自動重新生成身份認證中的 Session ID。否則,你需要手動呼叫 regenerate() 重新生成 Session ID
$request->session()->regenerate();
假如你需要重新生成 Session ID 並同時從Session中移除所有的資料,而且以單一命令來完成,可使用 invalidate()
$request->session()->invalidate();
Session 堵塞(進階技巧)
注意:
要利用 Session 堵塞,您的應用必須使用支持 atomic locks 的快取驅動。 當前這些快取驅動包括 memcached,dynamodb,redis 和 database 驅動。 另外也不能使用 cookie 驅動
預設情況下,Laravel 允許使用同一 Session 的請求同時執行。 例如如果你使用 JavaScript HTTP 庫向應用發出兩個 HTTP 請求,它們將同時執行。 對於許多應用來說,這不是問題。 但是在一小部分應用中可能會丟失 Session 資料,這些應用會向兩個不同的應用路由同時發出請求,這兩個路由都將資料寫入 Session
為了處理這種情況,Laravel 允許您限制指定 Session 的並發請求。 首先你可以簡單地在定義路由時呼叫 block()。 在這個例子中,進入到 /profile 路由的請求將添加 Session 鎖。 在 Session 鎖住期間,/ profile 或 / order 路由共享相同 session ID 的任何請求都將等待第一個請求執行完成,然後再繼續執行