Session
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 数组中,不会被持久化。
[!注意]
array
驱动主要用于测试,它会阻止会话数据被持久化保存。
驱动程序先决条件
数据库
如果你使用的是 database
会话驱动,就需要确保有一个数据库表来存储会话数据。通常,这个表会包含在 Laravel 默认的 0001_01_01_000000_create_users_table.php
数据库迁移 中;
但如果你没有 sessions
表,可以通过 Artisan 命令来生成会话表的迁移文件:
php artisan make:session-table
php artisan migrate
Redis
在与 Laravel 一起使用 Redis 会话之前,你需要通过 PECL 安装 PhpRedis PHP 扩展,或者通过 Composer 安装 predis/predis
包(~1.0)。有关配置 Redis 的更多信息,请查阅 Laravel 的 Redis 文档。
[!注意]
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']);
});
[!注意]
通过 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
[!警告]
要使用会话阻塞,你的应用程序必须使用支持原子锁的缓存驱动。
当前,这些缓存驱动包括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
。
这些方法的用途并不是一目了然,所以这里对每个方法的用途做一个概述:
open
方法 通常用于基于文件的会话存储系统。由于 Laravel 自带file
会话驱动,你几乎不需要在这个方法中写任何东西,可以直接留空。close
方法 和open
方法类似,大多数情况下也可以忽略。对大多数驱动来说它并不需要实现。read
方法 应该返回与给定$sessionId
相关联的会话数据的字符串形式。在你的驱动中获取或存储会话数据时,不需要做任何序列化或其它编码,因为 Laravel 会帮你完成序列化。write
方法 应该将与$sessionId
关联的$data
字符串写入某种持久化存储系统,比如 MongoDB 或你选择的其他存储系统。同样,不需要做序列化,Laravel 已经处理好了。destroy
方法 应该从持久化存储中移除与$sessionId
相关联的数据。gc
方法 应该销毁所有早于指定$lifetime
(UNIX 时间戳)的会话数据。对于像 Memcached 和 Redis 这种自动过期的系统,这个方法可以留空。
注册驱动
一旦你实现了驱动,就可以将它注册到 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
驱动指定为应用的会话驱动。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: