HTTP Session

简介

由于 HTTP 应用是无状态的,因此 Session 提供了一种在多个请求间存储用户相关信息的方法。Laravel 通过一个可读性强、统一的 API 访问处理各种自带的 Session 后端驱动。支持热门的诸如 MemcachedRedis 和开箱即用的数据库等驱动。

配置

Session 配置存储在 config/session.php 文件中。确保查看该文件中可用的选项。默认情况下,Laravel 配置使用 file Session 驱动,该驱动适用于许多应用。对于生产环境中的应用,可以考虑使用 memcachedredis 驱动来获取更快的 Session 性能。

Session 的 driver 配置项定义了每个请求存储 Session 数据的位置。Laravel 配备几个开箱即用的驱动:

  • file - Session 存储在 storage/framework/sessions
  • cookie - Session 存储在安全加密的 Cookie 中。
  • database - Session 存储在一个关系型数据库中。
  • memcachedredis - Session 存储在其中一个基于缓存的快速存储系统中。
  • array - Session 存储在一个 PHP 数组中,不会被持久化。

数组驱动在 测试 时使用,防止 Session 中存储的数据被持久化。

驱动前提

数据库

使用 database Session 驱动时,需要创建一张包含 Session 数据的表。以下是 Schema 声明该表的示例:

Schema::create('sessions', function ($table) {
    $table->string('id')->unique();
    $table->unsignedInteger('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 驱动之前,需要通过 Composer 安装 predis/predis 扩展包(~1.0)。可以在 database 配置文件中配置 Redis 连接。在 session 配置文件中,connection 选项可用于指定 Session 使用哪个 Redis 连接。

使用 Session

获取数据

在 Laravel 中主要有两种方法来处理 Session 数据:全局辅助函数 session 以及通过 Request 实例。首先,让我们看一下使用 Request 实例获取 Session,可以在控制器方法中对该实例使用类型提示。请记住,控制器方法的依赖会通过 Laravel 服务容器 自动注入:

namespace App\Http\Controllers;

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

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 方法,并且所请求的键不存在时,则会执行该闭包并返回其结果:

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

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

全局 Session 辅助函数

也可以使用全局辅助函数 session 来获取存储在 Session 中的数据。当用一个字符串参数调用 session 辅助函数时,会返回该 Session 键的值。当用键/值对调用时,这些值将会被存储到 Session 中:

Route::get('home', function () {
    // 获取 Session 中 key 的值
    $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 方法。如果指定项存在并且不为 nullhas 方法会返回 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()->flush();

重新生成 Session ID

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

如果使用内置的 LoginController,Laravel 会在身份认证时自动重新生成 Session ID;如果需要手动重新生成 Session ID,可以使用 regenerate 方法:

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

添加自定义 Session 驱动

实现驱动

自定义 Session 驱动应该实现 SessionHandlerInterface 接口。该接口只包含一些我们要实现的简单方法。一个缺省的 MongoDB 实现应该看起来像这样:

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 关联的 Session 数据从持久化存储中移除。
  • gc 方法应该销毁所有给定 $lifetime 之前的所有 Session 数据,它是一个 UNIX 时间戳。对本身拥有过期机制的系统如 Memcached 和 Redis,该方法可以留空。

注册驱动

驱动实现之后,就可以在框架中注册它了。要为 Laravel Session 后端添加其它驱动,可以使用 Session Facadeextend 方法。应该在 服务容器boot 方法中调用 extend 方法。可以在现有的 AppServiceProvider 或创建一个全新的服务提供者:

namespace App\Providers;

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

class SessionServiceProvider extends ServiceProvider
{
    /**
     * 启动已注册服务
     *
     * @return void
     */
    public function boot()
    {
        Session::extend('mongo', function ($app) {
            // 返回 SessionHandlerInterface 接口的实现
            return new MongoSessionHandler;
        });
    }

    /**
     * 注册容器绑定
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

注册 Session 驱动后,就可以在 config/session.php 配置文件中使用 mongo 驱动了。