Skip to content

HTTP 会话

简介

由于基于 HTTP 的应用程序是无状态的,会话提供了一种在多个请求之间存储用户信息的方法。这些用户信息通常放置在持久存储/后端中,可以从后续请求中访问。

Laravel 附带了各种会话后端,可以通过表达力强、统一的 API 进行访问。支持流行的后端,如 MemcachedRedis 和数据库。

配置

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

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

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

NOTE

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

驱动前提条件

数据库

使用 database 会话驱动时,你需要确保有一个数据库表来包含会话数据。通常,这包含在 Laravel 默认的 �001_01_01_000000_create_users_table.php 数据库迁移中;但是,如果出于任何原因你没有 sessions 表,你可以使用 make:session-table Artisan 命令生成此迁移:

`shell php artisan make:session-table

php artisan migrate


<a name="redis"></a>
#### Redis

在使用带有 Laravel 的 Redis 会话之前,你需要通过 PECL 安装 PhpRedis PHP 扩展,或通过 Composer 安装 predis/predis 包。有关配置 Redis 的更多信息,请查阅 Laravel 的 [Redis 文档](/docs/{{version}}/redis#configuration)。

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

<a name="interacting-with-the-session"></a>
## 与会话交互

<a name="retrieving-data"></a>
### 检索数据

在 Laravel 中处理会话数据有两种主要方式:全局的 session 辅助函数和通过 Request 实例。首先,让我们看看如何通过 Request 实例访问会话,该实例可以在路由闭包或控制器方法上进行类型提示。请记住,控制器方法的依赖关系是通过 Laravel [服务容器](/docs/{{version}}/container)自动注入的:

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * 显示给定用户的个人资料。
     */
    public function show(Request , string ): View
    {
         = ->session()->get('key');

        // ...

         = ->users->find();

        return view('user.profile', ['user' => ]);
    }
}

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

php
 = ->session()->get('key', 'default');

 = ->session()->get('key', function () {
    return 'default';
});

全局 Session 辅助函数

你也可以使用全局的 session PHP 函数来检索和存储会话中的数据。当使用单个字符串参数调用 session 辅助函数时,它将返回该会话键的值。当使用键/值对数组调用该辅助函数时,这些值将被存储在会话中:

php
Route::get('/home', function () {
    // 从会话中检索一条数据...
     = session('key');

    // 指定一个默认值...
     = session('key', 'default');

    // 在会话中存储一条数据...
    session(['key' => 'value']);
});

NOTE

通过 HTTP 请求实例与会话交互和使用全局的 session 辅助函数之间几乎没有实际区别。这两种方法都可以通过所有测试用例中可用的 ssertSessionHas 方法进行测试

检索所有会话数据

如果你想检索会话中的所有数据,可以使用 ll 方法:

php
 = ->session()->all();

检索部分会话数据

only 和 except 方法可用于检索会话数据的子集:

php
 = ->session()->only(['username', 'email']);

 = ->session()->except(['username', 'email']);

判断会话中是否存在项

要确定会话中是否存在某个项,可以使用 has 方法。如果该项存在且不为 ull,has 方法返回 rue:

php
if (->session()->has('users')) {
    // ...
}

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

php
if (->session()->exists('users')) {
    // ...
}

要确定会话中不存在某个项,可以使用 missing 方法。如果该项不存在,missing 方法返回 rue:

php
if (->session()->missing('users')) {
    // ...
}

存储数据

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

php
// 通过请求实例...
->session()->put('key', 'value');

// 通过全局 "session" 辅助函数...
session(['key' => 'value']);

推送到数组会话值

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

php
->session()->push('user.teams', 'developers');

检索并删除项

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

php
 = ->session()->pull('key', 'default');

递增和递减会话值

如果会话数据包含你想要递增或递减的整数,你可以使用 increment 和 decrement 方法:

php
->session()->increment('count');

->session()->increment('count',  = 2);

->session()->decrement('count');

->session()->decrement('count',  = 2);

闪存数据

有时你可能希望为下一个请求将会话项存储在会话中。你可以使用 lash 方法来实现。使用此方法存储在会话中的数据将立即可用,并在后续的 HTTP 请求期间可用。在后续的 HTTP 请求之后,闪存的数据将被删除。闪存数据主要用于短期的状态消息:

php
->session()->flash('status', 'Task was successful!');

如果你需要将闪存数据持久化多个请求,可以使用 eflash 方法,它将保留所有闪存数据用于额外的请求。如果你只需要保留特定的闪存数据,可以使用 keep 方法:

php
->session()->reflash();

->session()->keep(['username', 'email']);

要仅将闪存数据持久化到当前请求,你可以使用 ow 方法:

php
->session()->now('status', 'Task was successful!');

删除数据

orget 方法将从会话中移除一条数据。如果你想从会话中移除所有数据,可以使用 lush 方法:

php
// 忘记单个键...
->session()->forget('name');

// 忘记多个键...
->session()->forget(['name', 'status']);

->session()->flush();

重新生成会话 ID

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

如果你使用 Laravel 应用程序启动套件Laravel Fortify,Laravel 会在认证期间自动重新生成会话 ID;但是,如果你需要手动重新生成会话 ID,可以使用 egenerate 方法:

php
->session()->regenerate();

如果你需要在一条语句中重新生成会话 ID 并从会话中移除所有数据,可以使用 invalidate 方法:

php
->session()->invalidate();

会话缓存

Laravel 的会话缓存提供了一种便捷的方式来缓存限定于单个用户会话的数据。与全局应用程序缓存不同,会话缓存数据会自动按会话隔离,并在会话过期或销毁时清理。会话缓存支持所有熟悉的 Laravel 缓存方法,如 get、put、 emember、 orget 等,但限定于当前会话。

会话缓存非常适合存储临时的、特定于用户的数据,你希望在同一会话中的多个请求之间持久化这些数据,但不需要永久存储。这包括表单数据、临时计算、API 响应或任何其他应绑定到特定用户会话的临时数据。

你可以通过会话的 cache 方法访问会话缓存:

php
 = ->session()->cache()->get('discount');

->session()->cache()->put(
    'discount', 10, now()->plus(minutes: 5)
);

有关 Laravel 缓存方法的更多信息,请查阅缓存文档

会话阻塞

WARNING

要利用会话阻塞,你的应用程序必须使用支持原子锁的缓存驱动。目前,这些缓存驱动包括 memcached、dynamodb、 edis、mongodb(包含在官方 mongodb/laravel-mongodb 包中)、database、 ile 和 rray 驱动。此外,你不能使用 cookie 会话驱动。

默认情况下,Laravel 允许使用相同会话的请求并发执行。因此,例如,如果你使用 JavaScript HTTP 库向你的应用程序发出两个 HTTP 请求,它们将同时执行。对于许多应用程序来说,这不是问题;但是,在少数向两个不同的应用程序端点发出并发请求并且两者都会写入会话数据的应用程序中,可能会发生会话数据丢失。

为了缓解这种情况,Laravel 提供了允许你限制给定会话的并发请求的功能。首先,你只需将 lock 方法链式调用到你的路由定义上。在此示例中,对 /profile 端点的传入请求将获取会话锁。当持有此锁时,任何共享相同会话 ID 的对 /profile 或 /order 端点的传入请求都将等待第一个请求完成执行后再继续执行:

php
Route::post('/profile', function () {
    // ...
})->block( = 10,  = 10);

Route::post('/order', function () {
    // ...
})->block( = 10,  = 10);

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

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

如果这两个参数都没有传递,锁将最多持有 10 秒,并且请求在尝试获取锁时将最多等待 10 秒:

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

添加自定义会话驱动

实现驱动

如果现有的会话驱动都不满足你的应用程序需求,Laravel 使你可以编写自己的会话处理程序。你的自定义会话驱动应实现 PHP 内置的 SessionHandlerInterface。该接口只包含几个简单的方法。一个存根的 MongoDB 实现如下所示:

php
<?php

namespace App\Extensions;

class MongoSessionHandler implements \SessionHandlerInterface
{
    public function open(, ) {}
    public function close() {}
    public function read() {}
    public function write(, ) {}
    public function destroy() {}
    public function gc() {}
}

由于 Laravel 没有包含存放扩展的默认目录,你可以自由地将它们放在任何你喜欢的地方。在此示例中,我们创建了一个 Extensions 目录来存放 MongoSessionHandler。

由于这些方法的目的不是很容易理解,以下是每个方法用途的概述:

  • open 方法通常用于基于文件的会话存储系统。由于 Laravel 附带了一个 ile 会话驱动,你很少需要在此方法中放置任何内容。你可以简单地让此方法为空。
  • close 方法与 open 方法一样,通常也可以忽略。对于大多数驱动来说,它不是必需的。

ead 方法应返回与给定 $sessionId 关联的会话数据的字符串版本。在驱动中检索或存储会话数据时无需进行任何序列化或其他编码,因为 Laravel 会为你执行序列化。

  • write 方法应将与 $sessionId 关联的给定 $data 字符串写入某个持久存储系统,例如 MongoDB 或你选择的其他存储系统。同样,你不应执行任何序列化——Laravel 已经为你处理了这一点。
  • destroy 方法应从持久存储中删除与 $sessionId 关联的数据。
  • gc 方法应销毁所有早于给定 $lifetime(UNIX 时间戳)的会话数据。对于像 Memcached 和 Redis 这样的自过期系统,此方法可以留空。

注册驱动

一旦你的驱动已实现,就可以注册到 Laravel。要向 Laravel 的会话后端添加额外的驱动,你可以使用 Session 门面提供的 extend 方法。你应从服务提供者的 oot 方法中调用 extend 方法。你可以从现有的 App\Providers\AppServiceProvider 中执行此操作,或者创建一个全新的提供者:

php
<?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 ) {
            // 返回一个 SessionHandlerInterface 的实现...
            return new MongoSessionHandler;
        });
    }
}

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

基于 MIT 协议发布