Http 会话
介绍
由于基于 HTTP 的应用程序是无状态的,Session 提供了一种在多个请求之间存储用户信息的方式。这些用户信息通常会被放置在一个持久化存储后端中,并可以从后续的请求中访问。
配置
您的应用程序的会话配置文件存储在 config/session.php
中。务必检查此文件中提供的选项。默认情况下,Laravel 配置为使用 database
会话驱动程序。
session
驱动程序配置选项定义了每次请求时会话数据的存储位置。Laravel 包含了多种驱动程序:
-
file
:会话存储在storage/framework/sessions
目录中。 -
cookie
:会话存储在安全的、加密的 cookie 中。 -
database
:会话存储在关系型数据库中。 -
memcached
/redis
:会话存储在这两个快速的基于缓存的存储中。 -
dynamodb
:会话存储在 AWS DynamoDB 中。 -
array
:会话存储在 PHP 数组中,不会被持久化。
|
与会话交互
检索数据
Laravel 提供了两种主要的方式来处理会话数据:全局 session
助手函数和通过 Request
实例。首先,我们来看一下如何通过 Request
实例来访问会话数据,该实例可以在路由闭包或控制器方法中进行类型提示。请记住,控制器方法的依赖项会通过 Laravel 的【服务容器】自动注入:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
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 请求实例与使用全局 |
检索部分会话数据
您可以使用 only
和 except
方法来检索会话数据的子集:
$data = $request->session()->only(['username', 'email']);
$data = $request->session()->except(['username', 'email']);
确定会话项是否存在
要确定会话中是否存在某个项,您可以使用 has
方法。has
方法在该项存在并且不为 null
时返回 true
:
if ($request->session()->has('users')) {
// ...
}
要确定会话中是否存在某个项,即使它的值为 null
,您可以使用 exists
方法:
if ($request->session()->exists('users')) {
// ...
}
要确定会话中某项是否不存在,您可以使用 missing
方法。missing
方法在该项不存在时返回 true
:
if ($request->session()->missing('users')) {
// ...
}
存储数据
要将数据存储到会话中,通常可以使用请求实例的 put
方法或全局 session
助手函数:
// 通过请求实例...
$request->session()->put('key', 'value');
// 通过全局 "session" 助手函数...
session(['key' => 'value']);
闪存数据
有时您可能希望将项目存储在会话中,以便在下一个请求中使用。您可以使用 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!');
会话阻止
要使用会话阻塞功能,您的应用程序必须使用支持【原子锁】的缓存驱动。目前,支持原子锁的缓存驱动包括 |
默认情况下,Laravel 允许使用相同会话的请求并发执行。例如,如果您使用 JavaScript HTTP 库向应用程序发送两个 HTTP 请求,它们将同时执行。对于许多应用程序来说,这并不是问题;然而,在某些应用程序中,当两个不同的应用程序端点同时请求并写入会话数据时,可能会发生会话数据丢失。
为了减轻这个问题,Laravel 提供了功能,允许您限制针对给定会话的并发请求。您可以通过将 block
方法链接到路由定义来开始使用这个功能。在这个例子中,发送到 /profile
端点的请求将会获取会话锁。在此锁定期间,任何共享相同会话 ID 的请求(例如发送到 /profile
或 /order
端点的请求)将等待第一个请求执行完毕后再继续执行:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10);
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 已经提供了文件会话驱动程序,因此您很少需要在此方法中做任何事情。可以将该方法留空。
-
close:与
open
方法类似,close
方法通常也可以忽略。对于大多数驱动程序来说,它不需要做任何操作。 -
read:该方法应返回与给定
$sessionId
关联的会话数据的字符串版本。您不需要对会话数据进行序列化或其它编码操作,因为 Laravel 会为您处理序列化。 -
write:该方法应将与
$sessionId
关联的给定$data
字符串写入某个持久化存储系统,如 MongoDB 或您选择的其它存储系统。同样,您不需要进行序列化——Laravel 会为您处理。 -
destroy:该方法应从持久化存储中删除与
$sessionId
关联的数据。 -
gc:该方法应销毁所有比给定
$lifetime
更旧的会话数据,$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
驱动程序作为应用的会话驱动。