HTTP Session
简介
由于 HTTP 驱动的应用程序是无状态的,会话(Session)提供了一种在多个请求之间存储用户信息的方式。 这些用户信息通常被保存在一个持久化存储/后端中,以便后续请求能够访问。
Laravel 内置了多种会话后端,并通过一个简洁统一的 API 进行访问。 它支持常见的后端,例如 Memcached、Redis 和数据库。
配置
应用的会话配置文件位于 config/session.php。 你应该查看该文件中的可用选项。 默认情况下,Laravel 配置为使用 database 会话驱动。
会话的 driver 配置项定义了每次请求时会话数据的存储位置。Laravel 提供了多种驱动:
file—— 会话存储在storage/framework/sessions。cookie—— 会话存储在安全、加密的 Cookie 中。database—— 会话存储在关系型数据库中。memcached/redis—— 会话存储在这些基于缓存的高速存储中。dynamodb—— 会话存储在 AWS DynamoDB 中。array—— 会话存储在 PHP 数组中,不会被持久化。
Note
array 驱动主要用于测试,它会阻止会话数据被持久化保存。
驱动程序先决条件
数据库
如果你使用的是 database 会话驱动,就需要确保有一个数据库表来存储会话数据。通常,这个表会包含在 Laravel 默认的 0001_01_01_000000_create_users_table.php 数据库迁移 中; 但如果你没有 sessions 表,可以通过 Artisan 命令来生成会话表的迁移文件:
php artisan make:session-table
php artisan migrateRedis
在与 Laravel 一起使用 Redis 会话之前,你需要通过 PECL 安装 PhpRedis PHP 扩展,或者通过 Composer 安装 predis/predis 包(~1.0)。有关配置 Redis 的更多信息,请查阅 Laravel 的 Redis 文档。
Note
SESSION_CONNECTION 环境变量,或者 session.php 配置文件中的 connection 选项,可以用于指定用于会话存储的 Redis 连接。
使用 Session
获取数据
在 Laravel 中有两种主要方式处理会话数据:全局 session 辅助函数以及通过 Request 实例。首先,让我们看看通过 Request 实例访问会话,它可以在路由闭包或控制器方法上进行类型提示。记住,控制器方法依赖项会通过 Laravel 服务容器 自动注入:
<?php
namespace App\Http\Controllers;
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]);
}
}当你从会话中检索一个项目时,你也可以将默认值作为第二个参数传递给 get 方法。 如果指定的键在会话中不存在,这个默认值将会被返回。 如果你将闭包作为默认值传递给 get 方法,并且请求的键不存在,那么该闭包将会被执行,并返回其结果:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});全局 Session 辅助函数
你也可以使用全局的 session PHP 函数来检索和存储会话中的数据。 当 session 辅助函数只被传入一个字符串参数时,它会返回该会话键对应的值。 当该辅助函数被传入一个键/值对数组时,这些值会被存储到会话中:
Route::get('/home', function () {
// 从会话中获取一条数据...
$value = session('key');
// 指定一个默认值...
$value = session('key', 'default');
// 向会话中存储一条数据...
session(['key' => 'value']);
});Note
通过 HTTP 请求实例访问会话和使用全局 session 辅助函数在实际应用中几乎没有区别。 两种方法都可以通过 assertSessionHas 方法进行 可测试,该方法可在你的所有测试用例中使用。
获取所有会话数据
如果你想获取会话中的所有数据,可以使用 all 方法:
$data = $request->session()->all();获取 Session 数据的一部分
可以使用 only 和 except 方法来获取会话数据的一部分:
$data = $request->session()->only(['username', 'email']);
$data = $request->session()->except(['username', 'email']);确定 Session 中是否存在某项
要确定 Session 中是否存在某项,你可以使用 has 方法。如果该项存在且不为 null ,has 方法将返回 true。
if ($request->session()->has('users')) {
// ...
}要判断 Session 中是否存在某项,即使该项的值为 null ,你可以使用 exists 方法:
if ($request->session()->exists('users')) {
// ...
}要判断 Session 中不存在某项时,你可以使用 missing 方法。如果该项不存在,missing 方法将返回 true:
if ($request->session()->missing('users')) {
// ...
}存储数据
Session 里存储数据,你通常将使用 Request 实例中的 put 方法或者 session 助手函数:
// 通过 Request 实例存储 ...
$request->session()->put('key', 'value');
// 通过全局 Session 助手函数存储 ...
session(['key' => 'value']);向 Session 数组中添加元素
push 方法可以把一个新值推入到以数组形式存储的 session 值里。例如:如果 user.teams 键值对有一个关于团队名字的数组,你可以推入一个新值到这个数组里:
$request->session()->push('user.teams', 'developers');获取并删除数据
pull 方法可以在一次操作中同时获取并删除会话中的数据:
$value = $request->session()->pull('key', 'default');自增与自减会话值
如果会话中的某个值是整数,你可以用 increment 和 decrement 方法对它进行自增或自减:
$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 方法:
// 删除单个键
$request->session()->forget('name');
// 删除多个键
$request->session()->forget(['name', 'status']);
// 清空所有数据
$request->session()->flush();重新生成 Session ID
重新生成Session ID 通常是为了防止恶意用户利用你的应用程序进行会话固定攻击。
如果你使用的是 Laravel 的应用程序入门套件或 Laravel Fortify,Laravel 会在身份验证期间自动重新生成会话 ID; 但是,如果你需要手动重新生成会话 ID,你可以使用 regenerate 方法:
$request->session()->regenerate();如果你需要在一条语句中重新生成会话 ID 并移除会话中的所有数据,你可以使用 invalidate 方法:
$request->session()->invalidate();Session Blocking
Warning
要使用会话阻塞,你的应用程序必须使用支持原子锁的缓存驱动。 当前,这些缓存驱动包括 memcached、dynamodb、redis、mongodb(包含在官方的 mongodb/laravel-mongodb 包中)、database、file 和 array 驱动。 此外,你不能使用 cookie 会话驱动。
默认情况下,Laravel 允许使用相同会话的请求并发执行。 例如,如果你使用 JavaScript HTTP 库向你的应用程序发出两个 HTTP 请求,它们会同时执行。 对于许多应用程序来说,这不是问题; 但是,对于少数会同时向两个不同的应用端点发起并发请求且这两个端点都写入会话数据的应用来说,可能会发生会话数据丢失。
为了缓解这个问题,Laravel 提供了一个功能,可以限制给定会话的并发请求。 要开始使用,你只需要在路由定义上链式调用 block 方法。 在这个例子中,对 /profile 端点的传入请求会获取一个会话锁。 当这个锁被持有时,任何对 /profile 或 /order 端点的传入请求(共享相同会话 ID)都会等待第一个请求执行完毕后再继续执行:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);block 方法接收两个可选参数。 block 方法接收的第一个参数是会话锁在释放之前应被持有的最大秒数。 当然,如果请求在此时间之前执行完毕,则锁会更早被释放。
block 方法接收的第二个参数是请求在尝试获取会话锁时应等待的秒数。 如果请求在给定的秒数内无法获得会话锁,则会抛出 Illuminate\Contracts\Cache\LockTimeoutException 异常。
如果这两个参数都未传递,那么锁将最多被获取 10 秒,请求在尝试获取锁时也将最多等待 10 秒:
Route::post('/profile', function () {
// ...
})->block();添加自定义会话驱动
实现驱动
如果现有的会话驱动都不适合你的应用需求,Laravel 允许你编写自己的会话处理器。 你的自定义会话驱动应当实现 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。
这些方法的用途并不是一目了然,所以这里对每个方法的用途做一个概述:
注册驱动
一旦你实现了驱动,就可以将它注册到 Laravel 中。要向 Laravel 的会话后端添加额外驱动,可以使用 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_DRIVER 环境变量或者在应用的 config/session.php 配置文件中,将 mongo 驱动指定为应用的会话驱动。
本译文转载自 Laravel China 社区 组织翻译的 Laravel 中文文档。原文链接:https://learnku.com/docs/laravel/12.x/session/16954