15分鐘搞懂Laravel的Session機制

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 的任何請求都將等待第一個請求執行完成,然後再繼續執行


分享這篇文章:

關聯文章:

訂閱電子報,索取 Laravel 學習手冊

價值超過 3000 元,包含常用 Laravel 語法與指令!

一小時免費求職講座

3個應徵軟體工程師前該知道的秘訣

取得免費課程連結

Laravel 百萬年薪特訓營

從最基礎的 PHP 語法開始,包含所有你該知道的網頁基礎知識,連同 Laravel 從零開始一直到實戰,最後還將告訴你如何找好工作,讓你及早擁有百萬年薪