Session

未匹配的标注
本文档最新版为 10.x,旧版本可能放弃维护,推荐阅读最新版!

HTTP 会话机制#

简介#

由于 HTTP 驱动的应用程序是无状态的,Session 提供了一种在多个请求之间存储有关用户信息的方法,Laravel 通过同一个可读性强的 API 处理各种自带的后台驱动程序。支持诸如比较热门的 Memcached, Redis 和数据库。

配置#

Session 的配置文件存储在 config/session.php 文件中。请务必查看此文件中对于你而言可用的选项。默认情况下,Laravel 为绝大多数应用程序配置的 Session 驱动为 file 。在生产环境中,你可以考虑使用 memcachedredis 驱动,让 Session 的性能更加出色。

Session driver 的配置预设了每个请求存储 Session 数据的位置。Laravel 自带了几个不错而且开箱即用的驱动:

  • file - 将 Session 存储在 storage/framework/sessions 中。
  • cookie - Sessions 被存储在安全加密的 cookie 中。
  • database - Sessions 被存储在关系型数据库中。
  • memcached / redis - Sessions 被存储在基于高速缓存的存储系统中。
  • array - Sessions 存储在 PHP 数组中,但不会被持久化。

技巧:数组驱动一般用于 测试 并且防止存储在 Session 中的数据被持久化。

驱动程序先决条件#

数据库#

使用 database 作为 Session 驱动时,你需要创建一张包含 Session 各项数据的表。以下是使用 Schema 建表的例子:

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');
});

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

php artisan session:table

php artisan migrate

Redis#

Laravel 在使用 Redis 作为 Session 驱动之前,需要安装 Redis 的 PHP 扩展或者通过 Composer 安装 predis/predis 扩展包 (~1.0)。Redis 的详细配置信息参考 Laravel Redis 文档

技巧:在 session 配置文件中,connection 选项可用于指定会话使用哪个 Redis 连接。

使用 Session#

获取数据#

Laravel 中处理 Session 数据有两种主要方法:全局辅助函数 session 和通过一个 Request 实例。首先,我们来看看通过控制器方法类型提示一个 Request 实例来访问 session。控制器方法依赖项会通过 Laravel 服务容器 实现自动注入:

<?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');

        //
    }
}

当你从 Session 获取值时,你还可以传递一个默认值作为 get 方法的第二个参数。如果 Session 中不存在指定的键,便会返回这个默认值。若传递一个闭包作为 get 方法的默认值,并且所请求的键并不存在时,get 方法将执行闭包并返回其结果:

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

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

全局辅助函数 Session#

你也可以使用全局的 PHP 辅助函数 session 来获取和存储 Session 数据。 使用单个字符串类型的值作为参数调用辅助函数 session 时,它会返回该字该符串对应的 Session 键的值。当使用一个键值对数组作为参数调用辅助函数 session 时,传入的键值将会存储在 Session 中:

Route::get('home', function () {
    // 获取 session 中的一条数据...
    $value = session('key');

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

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

技巧:通过 HTTP 请求实例操作 Session 与使用全局辅助函数 session 两者之间并没有实质上的区别。这两种方法都可以通过所有测试用例中可用的 assertSessionHas 方法进行 测试

获取所有 Session 数据#

如果你想获取 session 中的所有数据,可以使用 all 方法:

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

判断 Session 中是否存在某个值#

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

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

要确定 Session 中是否存在某个值,即使其值为 null,也可以使用 exists 方法。如果值存在,则 exists 方法返回 true

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

存储数据#

想要存储数据到 Session,你可以使用 put 方法,或者使用辅助函数 session

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

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

保存数据到 Session 数组中#

push 方法可以将一个新的值添加到 Session 数组内。例如,假设 user.teams 这个键是包括团队名称的数组,你可以这样将一个新的值加入到数组中:

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

检索 & 删除一条数据#

pull 方法可以只使用一条语句就从 Session 中检索并删除一条语句:

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

闪存数据#

有时候你可能想在 Session 中保存数据用于下一次请求,这时你可以使用 flash 方法。使用这个方法保存在 Session 中的数据,只会保留到下一个 HTTP 请求到来之前,然后就会被删除。闪存数据主要用于短期的状态消息:

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

如果你需要在更多的请求中使用到该一次性数据,你可以使用 reflash 方法,该方法会将所有一次性请求保留到下一次请求。如果你想保存一次性数据,你可以用 keep 方法:

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

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

删除数据#

forget 方法会从 Session 中删除指定数据,如果想从 Session 中删除所有数据,可以使用 flush 方法:

// 删除单个值...
$request->session()->forget('key');

// 删除多个值...
$request->session()->forget(['key1', 'key2']);

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

重新生成 Session ID#

重新生成 session ID 通常是为了防止恶意用户利用 session fixation 对你的应用进行攻击。

如果你使用了内置的 LoginController,Laravel 会自动重新生成身份认证中的 Session ID。否则,你需要手动使用 regenerate 方法重新生成 Session ID。

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

Session 阻塞#

注意:要利用会话阻止,您的应用程序必须使用支持 atomic locks 的缓存驱动。 当前,这些缓存驱动包括 memcacheddynamodbredisdatabase 驱动程序。 另外,您不能使用 cookie 驱动。

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

为了处理这种情况,Laravel 允许您限制指定会话的并发请求。 首先,您可以简单地在定义路由时调用 block 方法。 在这个例子中,进入到 /profile 端点的请求将添加会话锁。 在会话锁期间,/ profile/ order 端点共享相同 session ID 的任何请求都将等待第一个请求执行完成,然后再继续执行:

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()

添加自定义 Session 驱动#

实现驱动#

你自定义的 Session 驱动必须实现 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 方法通常用于基于文件的 Session 存储系统。因为 Laravel 已经附带了一个 file Session 驱动。所以你不需要在该方法中放置任何代码。PHP 要求必须要有这个方法的实现(这只是一个糟糕的接口设计),你只需要把这个方法置空。
  • close 方法跟 open 方法相似,通常也可以被忽略。对大多数的驱动而言,此方法不是必须的。
  • read 方法应当返回与给定的 $sessionId 相匹配的 Session 数据的字符串格式。在你的自定义的驱动中获取或存储 Session 数据时,不需要进行任何序列化或者其他编码,因为 Laravel 会自动为你执行序列化。
  • write 方法将与 $sessionId 关联的给定的 $data 字符串写入到一些持久化存储系统,如 MongoDB、Dynamo 等。再次重申,你不需要进行任何序列化或其他编码,因为 Laravel 会自动为你处理这些事情。
  • destroy 方法将会从持久化存储中删除与 $sessionId 相关的数据。
  • gc 方法能销毁给定的 $lifetime(UNIX 的时间戳)之前的所有数据。对本身拥有过期机制的系统如 Memcached 和 Redis 而言,该方法可以置空。

注册驱动#

当实现驱动后,需要在框架中注册它。在 Laravel 后端添加额外的驱动,需要使用 Session facadeextend 方法。你应该在 服务提供者 中的 boot 方法中调用 extend 方法。你可以在已有的 AppServiceProvider 或者另外创建一个服务提供者执行此操作:

<?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) {
            // Return implementation of SessionHandlerInterface...
            return new MongoSessionHandler;
        });
    }
}

驱动完成注册时,你可以在使用在配置文件 config/session.php 中使用 mongo 驱动。

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/laravel/8.x/ses...

译文地址:https://learnku.com/docs/laravel/8.x/ses...

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:2
讨论数量: 1
发起讨论 只看当前版本


KeenSting
session put 失效问题 需要注意的点
6 个点赞 | 3 个回复 | 分享 | 课程版本 5.5