HTTP Session
简介
由于 HTTP 驱动的应用程序是无状态的,Session 提供了一种在多个请求之间存储用户信息的方法。这些用户信息通常会被放置在持久化存储/后端中,以便后续请求能够访问。
Laravel 附带了多种 Session 后端,通过富有表现力且统一的 API 进行访问。它支持诸如 Memcached、Redis 和数据库等流行的后端。
配置
应用程序的 Session 配置文件存储在 config/session.php 中。请务必查看该文件中可用的选项。默认情况下,Laravel 配置为使用 database Session 驱动。
Session 的 driver 配置项定义了每个请求的 Session 数据存储在哪里。Laravel 包含多种驱动:
file- Session 存储在storage/framework/sessions中。cookie- Session 存储在安全且加密的 Cookie 中。database- Session 存储在关系型数据库中。memcached/redis- Session 存储在这些基于缓存的高速存储中。dynamodb- Session 存储在 AWS DynamoDB 中。array- Session 存储在 PHP 数组中,不会被持久化。
array 驱动主要用于 测试,它会防止 Session 中存储的数据被持久化。
驱动前提条件
数据库
使用 database Session 驱动时,需要确保拥有一个用于存放 Session 数据的数据库表。通常,这包含在 Laravel 默认的 0001_01_01_000000_create_users_table.php 数据库迁移文件中;然而,如果由于某种原因你没有 sessions 表,可以使用 make:session-table Artisan 命令来生成该迁移。
1php artisan make:session-table2 3php artisan migrate
Redis
在使用 Redis Session 之前,需要通过 PECL 安装 PhpRedis PHP 扩展,或者通过 Composer 安装 predis/predis 包 (~1.0)。有关配置 Redis 的更多信息,请查阅 Laravel 的 Redis 文档。
可以使用 SESSION_CONNECTION 环境变量,或 session.php 配置文件中的 connection 选项来指定用于 Session 存储的 Redis 连接。
与 Session 交互
获取数据
在 Laravel 中使用 Session 数据主要有两种方式:全局 session 辅助函数和通过 Request 实例。首先,让我们看看如何通过 Request 实例访问 Session,它可以被类型提示注入到路由闭包或控制器方法中。请记住,控制器方法依赖项会自动通过 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): View14 {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}
当从 Session 中检索项时,可以将默认值作为 get 方法的第二个参数传递。如果指定的键在 Session 中不存在,将返回此默认值。如果你传递一个闭包作为 get 方法的默认值,且请求的键不存在,该闭包将被执行并返回其结果。
1$value = $request->session()->get('key', 'default');2 3$value = $request->session()->get('key', function () {4 return 'default';5});
全局 Session 辅助函数
你还可以使用全局 session PHP 函数来检索和存储 Session 数据。当使用单个字符串参数调用 session 辅助函数时,它将返回该 Session 键的值。当使用键值对数组调用辅助函数时,这些值将被存储在 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 与使用全局 session 辅助函数之间几乎没有实际区别。这两种方法都可以通过在所有测试用例中可用的 assertSessionHas 方法进行 测试。
获取所有 Session 数据
如果你想获取 Session 中的所有数据,可以使用 all 方法。
1$data = $request->session()->all();
获取部分 Session 数据
only 和 except 方法可用于获取 Session 数据的一个子集。
1$data = $request->session()->only(['username', 'email']);2 3$data = $request->session()->except(['username', 'email']);
判断 Session 中是否存在某项
要判断某项是否存在于 Session 中,可以使用 has 方法。如果该项存在且不为 null,has 方法返回 true。
1if ($request->session()->has('users')) {2 // ...3}
即使某项的值为 null,若要判断其是否存在于 Session 中,可以使用 exists 方法。
1if ($request->session()->exists('users')) {2 // ...3}
若要判断某项不存在于 Session 中,可以使用 missing 方法。如果该项不存在,missing 方法返回 true。
1if ($request->session()->missing('users')) {2 // ...3}
存储数据
要将数据存储到 Session 中,通常使用请求实例的 put 方法或全局 session 辅助函数。
1// Via a request instance...2$request->session()->put('key', 'value');3 4// Via the global "session" helper...5session(['key' => 'value']);
向数组 Session 值中追加数据
push 方法可用于将一个新值推入数组形式的 Session 值中。例如,如果 user.teams 键包含一个团队名称数组,你可以像这样将新值推入该数组。
1$request->session()->push('user.teams', 'developers');
检索并删除某项
pull 方法可以在单条语句中检索并删除 Session 中的某项。
1$value = $request->session()->pull('key', 'default');
递增和递减 Session 值
如果你的 Session 数据包含一个需要递增或递减的整数,可以使用 increment 和 decrement 方法。
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 Data)
有时你可能希望在下一次请求中临时存储一些数据,可以使用 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 方法将从 Session 中移除一条数据。如果你想移除 Session 中的所有数据,可以使用 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();
重新生成 Session ID
重新生成 Session ID 通常是为了防止恶意用户利用应用程序的 会话固定 (Session fixation) 攻击。
如果你使用的是 Laravel 入门套件 或 Laravel Fortify,Laravel 会在认证期间自动重新生成 Session ID;但如果你需要手动重新生成 Session ID,可以使用 regenerate 方法。
1$request->session()->regenerate();
如果你需要在单条语句中重新生成 Session ID 并清除 Session 中的所有数据,可以使用 invalidate 方法。
1$request->session()->invalidate();
Session 缓存
Laravel 的 Session 缓存提供了一种便捷的方式来缓存作用于单个用户 Session 的数据。与全局应用程序缓存不同,Session 缓存数据会自动隔离,并在 Session 过期或销毁时被清理。Session 缓存支持所有熟悉的 Laravel 缓存方法,如 get、put、remember、forget 等,但仅限于当前 Session。
Session 缓存非常适合存储临时的、特定于用户的数据,这些数据需要在同一 Session 的多个请求之间保留,但无需永久存储。这包括表单数据、临时计算结果、API 响应或任何其他应绑定到特定用户 Session 的瞬态数据。
你可以通过 Session 上的 cache 方法访问 Session 缓存。
1$discount = $request->session()->cache()->get('discount');2 3$request->session()->cache()->put(4 'discount', 10, now()->plus(minutes: 5)5);
有关 Laravel 缓存方法的更多信息,请查阅 缓存文档。
Session 阻塞
要使用 Session 阻塞,你的应用程序必须使用支持 原子锁 (Atomic locks) 的缓存驱动。目前,这些缓存驱动包括 memcached、dynamodb、redis、mongodb(包含在官方的 mongodb/laravel-mongodb 包中)、database、file 和 array 驱动。此外,不能使用 cookie Session 驱动。
默认情况下,Laravel 允许使用同一 Session 的请求并发执行。例如,如果你使用 JavaScript HTTP 库向应用程序发送两个 HTTP 请求,它们会同时执行。对于许多应用程序来说,这不是问题;但在少数应用程序中,如果并发请求同时写入 Session,可能会导致 Session 数据丢失。
为了缓解这个问题,Laravel 提供了限制给定 Session 并发请求的功能。要开始使用,只需在路由定义中链式调用 block 方法。在此示例中,对 /profile 终结点的传入请求将获取一个 Session 锁。持有此锁期间,任何共享相同 Session ID 且访问 /profile 或 /order 终结点的请求都将等待第一个请求执行完毕后再继续执行。
1Route::post('/profile', function () {2 // ...3})->block($lockSeconds = 10, $waitSeconds = 10);4 5Route::post('/order', function () {6 // ...7})->block($lockSeconds = 10, $waitSeconds = 10);
block 方法接受两个可选参数。第一个参数是 Session 锁在释放前应保持的最长秒数。当然,如果请求在此时间内执行完毕,锁会提前释放。
block 方法接受的第二个参数是请求在尝试获取 Session 锁时应等待的秒数。如果请求无法在给定时间内获取到 Session 锁,将抛出 Illuminate\Contracts\Cache\LockTimeoutException 异常。
如果未传递这些参数,锁最多持有 10 秒,请求在尝试获取锁时最多等待 10 秒。
1Route::post('/profile', function () {2 // ...3})->block();
添加自定义 Session 驱动
实现驱动
如果现有的 Session 驱动均无法满足你的应用程序需求,Laravel 允许编写自定义的 Session 处理程序。自定义 Session 驱动应实现 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方法通常用于基于文件的 Session 存储系统。由于 Laravel 附带了fileSession 驱动,你几乎不需要在此方法中填入任何内容。你可以简单地将其留空。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 中,或创建一个全新的提供者。
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 ServiceProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 // ...18 }19 20 /**21 * Bootstrap any application services.22 */23 public function boot(): void24 {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 指定为应用程序的 Session 驱动。