跳至内容

HTTP 会话

介绍

由于 HTTP 驱动的应用程序是无状态的,因此会话提供了一种跨多个请求存储用户信息的方法。该用户信息通常存储在持久化存储/后端中,以便后续请求可以访问。

Laravel 内置了多种会话后端,可通过统一且富有表现力的 API 进行访问。它支持MemcachedRedis和数据库等常用后端。

配置

应用程序的会话配置文件存储在config/session.php。请务必查看此文件中可用的选项。默认情况下,Laravel 配置为使用database会话驱动程序。

会话driver配置选项定义了每个请求的会话数据的存储位置。Laravel 包含多种驱动程序:

  • file- 会话存储在 中storage/framework/sessions
  • cookie- 会话存储在安全、加密的 cookie 中。
  • database- 会话存储在关系数据库中。
  • memcached/ redis- 会话存储在这些快速的、基于缓存的存储库之一中。
  • dynamodb- 会话存储在 AWS DynamoDB 中。
  • array- 会话存储在 PHP 数组中,不会持久保存。

数组驱动程序主要用于测试期间,并防止会话中存储的数据被持久化。

驱动程序先决条件

数据库

使用databaseSession 驱动时,你需要确保有一个数据库表来保存 Session 数据。通常,这包含在 Laravel 的默认0001_01_01_000000_create_users_table.php 数据库迁移中;但是,如果你因为某种原因没有sessions数据库表,可以使用make:session-tableArtisan 命令来生成此迁移:

1php artisan make:session-table
2 
3php artisan migrate

Redis

在 Laravel 中使用 Redis 会话之前,您需要通过 PECL 安装 PhpRedis PHP 扩展,或predis/predis通过 Composer 安装该软件包 (~1.0)。有关配置 Redis 的更多信息,请参阅 Laravel 的Redis 文档

环境SESSION_CONNECTION变量或配置文件connection中的选项session.php可用于指定哪个 Redis 连接用于会话存储。

与会话交互

检索数据

在 Laravel 中,处理会话数据主要有两种方式:全局session辅助方法和实例Request。首先,我们来看看如何通过Request实例访问会话,实例可以在路由闭包或控制器方法上进行类型Prompts。记住,控制器方法的依赖关系会通过 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): View
14 {
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}

从会话中检索项目时,还可以将默认值作为第二个参数传递给该get方法。如果会话中不存在指定的键,则返回此默认值。如果将闭包作为默认值传递给该get方法,并且请求的键不存在,则将执行该闭包并返回其结果:

1$value = $request->session()->get('key', 'default');
2 
3$value = $request->session()->get('key', function () {
4 return 'default';
5});

全局会话Helpers

您还可以使用全局sessionPHP 函数在会话中检索和存储数据。当session使用单个字符串参数调用此Helpers时,它将返回该会话键的值。当使用键/值对数组调用此Helpers时,这些值将存储在会话中:

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辅助方法之间几乎没有实际区别。这两种方法都可以通过所有测试用例中可用的方法来测试。assertSessionHas

检索所有会话数据

如果您想要检索会话中的所有数据,您可以使用该all方法:

1$data = $request->session()->all();

检索部分会话数据

方法可用于检索会话数据的子集onlyexcept

1$data = $request->session()->only(['username', 'email']);
2 
3$data = $request->session()->except(['username', 'email']);

确定会话中是否存在某个项目

要确定某个项目是否存在于会话中,可以使用该has方法。如果项目存在,则该方法返回结果。如果项目不存在,has则返回结果truenull

1if ($request->session()->has('users')) {
2 // ...
3}

要确定会话中是否存在某个项目,即使其值为null,您也可以使用该exists方法:

1if ($request->session()->exists('users')) {
2 // ...
3}

要判断某个项目是否在 session 中不存在,可以使用该missing方法。如果该项目不存在,该missing方法将返回:true

1if ($request->session()->missing('users')) {
2 // ...
3}

存储数据

要在会话中存储数据,通常使用请求实例的put方法或全局session帮助程序:

1// Via a request instance...
2$request->session()->put('key', 'value');
3 
4// Via the global "session" helper...
5session(['key' => 'value']);

推送至数组会话值

push方法可用于将新值推送到作为数组的会话值。例如,如果user.teams键包含一个球队名称数组,则可以像这样将新值推送到数组中:

1$request->session()->push('user.teams', 'developers');

检索和删除项目

pull方法将通过一条语句从会话中检索并删除一个项目:

1$value = $request->session()->pull('key', 'default');

增加和减少会话值

如果您的会话数据包含您希望增加或减少的整数,则可以使用incrementdecrement方法:

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方法来实现。使用此方法存储在会话中的数据将立即可用,并在后续 HTTP 请求期间可用。在后续 HTTP 请求之后,闪存数据将被删除。闪存数据主要用于短期状态消息:

1$request->session()->flash('status', 'Task was successful!');

如果您需要为多个请求保留 Flash 数据,可以使用该reflash方法,该方法将保留所有 Flash 数据以供下次请求使用。如果您只需要保留特定的 Flash 数据,可以使用该keep方法:

1$request->session()->reflash();
2 
3$request->session()->keep(['username', 'email']);

要仅为当前请求保留闪存数据,您可以使用该now方法:

1$request->session()->now('status', 'Task was successful!');

删除数据

forget方法将从会话中删除一条数据。如果您想从会话中删除所有数据,可以使用该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();

重新生成会话 ID

重新生成会话 ID 通常是为了防止恶意用户利用会话固定攻击您的应用程序。

如果您使用 Laravel应用程序入门套件Laravel Fortify之一,Laravel 会在身份验证期间自动重新生成会话 ID ;但是,如果您需要手动重新生成会话 ID,则可以使用该regenerate方法:

1$request->session()->regenerate();

如果您需要重新生成会话 ID 并在单个语句中从会话中删除所有数据,则可以使用该invalidate方法:

1$request->session()->invalidate();

会话阻止

要使用会话阻塞功能,您的应用程序必须使用支持原子锁的缓存驱动程序。目前,这些缓存驱动程序包括memcacheddynamodbredismongodb包含在官方mongodb/laravel-mongodb软件包中)、databasefilearray驱动程序。此外,您不能使用cookie会话驱动程序。

默认情况下,Laravel 允许使用同一会话的请求并发执行。例如,如果您使用 JavaScript HTTP 库向应用程序发出两个 HTTP 请求,它们将同时执行。对于许多应用程序来说,这并非问题;然而,一小部分应用程序可能会发生会话数据丢失,因为它们会并发请求两个不同的应用程序端点,并将数据写入会话。

为了缓解这种情况,Laravel 提供了限制特定会话并发请求的功能。首先,你可以简单地将该block方法链接到路由定义中。在本例中,传入到 端点的请求/profile将获取会话锁。在持有此锁期间,任何传入到/profile/order端点且共享相同会话 ID 的请求都将等待第一个请求执行完毕,然后才能继续执行:

1Route::post('/profile', function () {
2 // ...
3})->block($lockSeconds = 10, $waitSeconds = 10);
4 
5Route::post('/order', function () {
6 // ...
7})->block($lockSeconds = 10, $waitSeconds = 10);

block方法接受两个可选参数。第一个参数block是会话锁在释放前应保持的最大秒数。当然,如果请求在此时间之前完成执行,锁将提前释放。

该方法接受的第二个参数block是请求尝试获取会话锁时应等待的秒数。Illuminate\Contracts\Cache\LockTimeoutException如果请求在给定的秒数内无法获取会话锁,则会抛出一个异常。

如果没有传递这两个参数,则最多可以获得 10 秒的锁,并且请求在尝试获取锁时最多等待 10 秒:

1Route::post('/profile', function () {
2 // ...
3})->block();

添加自定义会话驱动程序

实现驱动程序

如果现有的 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方法通常用于基于文件的会话存储系统。由于 Laravel 自带了file会话驱动程序,因此您很少需要在此方法中输入任何内容。您可以直接将此方法留空。
  • close方法与open方法一样,通常也可以忽略。对于大多数驾驶员来说,它没有必要。
  • read方法应该返回与给定 关联的会话数据的字符串版本$sessionId。在驱动程序中检索或存储会话数据时,无需进行任何序列化或其他编码,因为 Laravel 将为您执行序列化。
  • write方法应该将$data与 关联的给定字符串写入$sessionId某个持久存储系统,例如 MongoDB 或您选择的其他存储系统。同样,您不需要执行任何序列化操作 - Laravel 已经为您处理好了。
  • destroy方法应该从持久存储中删除与之相关的数据$sessionId
  • gc方法应销毁所有早于给定$lifetime(UNIX 时间戳)的会话数据。对于 Memcached 和 Redis 等自过期系统,此方法可以留空。

注册驱动程序

一旦你的驱动程序实现完毕,你就可以将其注册到 Laravel 中了。要将其他驱动程序添加到 Laravel 的会话后端,你可以使用Facadeextend提供的方法。你应该服务提供者的方法中调用该方法。你可以从现有的提供者中执行此操作,也可以创建一个全新的提供者:Session extendbootApp\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 ServiceProvider
11{
12 /**
13 * Register any application services.
14 */
15 public function register(): void
16 {
17 // ...
18 }
19 
20 /**
21 * Bootstrap any application services.
22 */
23 public function boot(): void
24 {
25 Session::extend('mongo', function (Application $app) {
26 // Return an implementation of SessionHandlerInterface...
27 return new MongoSessionHandler;
28 });
29 }
30}

一旦会话驱动程序被注册,您就可以使用环境变量或在应用程序的配置文件中mongo将该驱动程序指定为应用程序的会话驱动程序。SESSION_DRIVERconfig/session.php