Skip to content
赞助商
虚位以待
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

HTTP 会话

介绍

由于 HTTP 驱动的应用程序是无状态的,会话提供了一种在多个请求之间存储用户信息的方法。Laravel 附带了多种会话后端,可以通过一个富有表现力的统一 API 进行访问。支持流行的后端如 MemcachedRedis 和数据库的支持是开箱即用的。

配置

会话配置文件存储在 config/session.php。请务必查看此文件中可用的选项。默认情况下,Laravel 配置为使用 file 会话驱动程序,这对于许多应用程序来说都能很好地工作。

会话 driver 配置选项定义了会话数据在每个请求中将存储的位置。Laravel 附带了几个出色的驱动程序:

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

NOTE

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

驱动程序先决条件

数据库

使用 database 会话驱动程序时,您需要创建一个表来包含会话项。以下是表的 Schema 声明示例:

php
Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->foreignId('user_id')->nullable();
    $table->string('ip_address', 45)->nullable();
    $table->text('user_agent')->nullable();
    $table->text('payload');
    $table->integer('last_activity');
});

您可以使用 session:table Artisan 命令生成此迁移:

php
php artisan session:table

php artisan migrate

Redis

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

NOTE

session 配置文件中,可以使用 connection 选项指定会话使用的 Redis 连接。

使用会话

检索数据

在 Laravel 中有两种主要方式处理会话数据:全局 session 助手和通过 Request 实例。首先,让我们看看通过 Request 实例访问会话,这可以在控制器方法中进行类型提示。请记住,控制器方法依赖项是通过 Laravel 服务容器自动注入的:

php
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 显示给定用户的个人资料。
     *
     * @param  Request  $request
     * @param  int  $id
     * @return Response
     */
    public function show(Request $request, $id)
    {
        $value = $request->session()->get('key');

        //
    }
}

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

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

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

全局会话助手

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

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

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

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

NOTE

通过 HTTP 请求实例使用会话与使用全局 session 助手之间几乎没有实际区别。两种方法都可以通过 assertSessionHas 方法进行测试,该方法在所有测试用例中可用。

检索所有会话数据

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

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

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

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

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

要确定会话中是否存在某个项目,即使其值为 null,可以使用 exists 方法。exists 方法返回 true,如果该项目存在:

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

存储数据

要在会话中存储数据,通常使用 put 方法或 session 助手:

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

// 通过全局助手...
session(['key' => 'value']);

推送到数组会话值

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

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

检索并删除项目

pull 方法将在单个语句中检索并删除会话中的项目:

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

闪存数据

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

php
$request->session()->flash('status', '任务成功完成!');

如果您需要将闪存数据保留几个请求,可以使用 reflash 方法,这将保留所有闪存数据以供额外请求使用。如果您只需要保留特定的闪存数据,可以使用 keep 方法:

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

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

删除数据

forget 方法将从会话中删除一条数据。如果您想删除会话中的所有数据,可以使用 flush 方法:

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

// 忘记多个键...
$request->session()->forget(['key1', 'key2']);

$request->session()->flush();

重新生成会话 ID

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

如果您使用内置的 LoginController,Laravel 会在身份验证期间自动重新生成会话 ID;但是,如果您需要手动重新生成会话 ID,可以使用 regenerate 方法。

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

会话阻塞

NOTE

要利用会话阻塞,您的应用程序必须使用支持原子锁的缓存驱动程序。目前,这些缓存驱动程序包括 memcacheddynamodbredisdatabase 驱动程序。此外,您不能使用 cookie 会话驱动程序。

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

为了解决这个问题,Laravel 提供了限制给定会话的并发请求的功能。要开始,您可以简单地将 block 方法链接到您的路由定义中。在此示例中,对 /profile 端点的传入请求将获取会话锁。在此锁定期间,任何与相同会话 ID 共享的 /profile/order 端点的传入请求将等待第一个请求完成执行,然后再继续执行:

php
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 秒:

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

添加自定义会话驱动程序

实现驱动程序

您的自定义会话驱动程序应实现 SessionHandlerInterface。此接口包含我们需要实现的几个简单方法。一个存根的 MongoDB 实现看起来像这样:

php
<?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) {}
}

NOTE

Laravel 不附带包含您的扩展的目录。您可以将它们放置在任何您喜欢的地方。在此示例中,我们创建了一个 Extensions 目录来存放 MongoSessionHandler

由于这些方法的目的不易理解,让我们快速介绍一下每个方法的作用:

  • open 方法通常用于基于文件的会话存储系统。由于 Laravel 附带了一个 file 会话驱动程序,您几乎不需要在此方法中放置任何内容。您可以将其保留为空存根。这是 PHP 需要我们实现此方法的糟糕接口设计的一个事实(我们将在后面讨论)。
  • close 方法与 open 方法一样,通常也可以忽略。对于大多数驱动程序来说,它是不需要的。
  • read 方法应返回与给定 $sessionId 关联的会话数据的字符串版本。在检索或存储会话数据时,无需进行任何序列化或其他编码,因为 Laravel 会为您执行序列化。
  • write 方法应将与 $sessionId 关联的给定 $data 字符串写入某个持久存储系统,例如 MongoDB、Dynamo 等。同样,您不应进行任何序列化 - Laravel 已经为您处理了。
  • destroy 方法应从持久存储中删除与 $sessionId 关联的数据。
  • gc 方法应销毁所有早于给定 $lifetime 的会话数据,这是一个 UNIX 时间戳。对于自我过期系统如 Memcached 和 Redis,此方法可以留空。

注册驱动程序

一旦实现了驱动程序,您就可以准备将其注册到框架中。要向 Laravel 的会话后端添加其他驱动程序,可以在 Session facade 上使用 extend 方法。您应该在服务提供者boot 方法中调用 extend 方法。您可以在现有的 AppServiceProvider 中执行此操作,也可以创建一个全新的提供者:

php
<?php

namespace App\Providers;

use App\Extensions\MongoSessionHandler;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;

class SessionServiceProvider extends ServiceProvider
{
    /**
     * 注册任何应用程序服务。
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * 启动任何应用程序服务。
     *
     * @return void
     */
    public function boot()
    {
        Session::extend('mongo', function ($app) {
            // 返回 SessionHandlerInterface 的实现...
            return new MongoSessionHandler;
        });
    }
}

一旦会话驱动程序注册完毕,您可以在 config/session.php 配置文件中使用 mongo 驱动程序。