会话

未匹配的标注

HTTP 会话机制

简介

由于 HTTP 驱动的应用程序是无状态的,因此 Session 提供了一种在多个请求之间存储有关用户信息的方法, 用户信息通常放置在可从后续请求访问的持久性存储/后端中。

Laravel 提供了各种会话驱动程序,可以通过一个富有表现力的、统一的API访问。支持流行的驱动程序,如 MemcachedRedis 和数据库。

配置

你应用程序的会话配置文件存储在 config/session.php。请务必查看此文件中对于你而言可用的选项。默认情况下,Laravel 为绝大多数应用程序配置的 Session 驱动为 file这对于许多应用程序都能很好地工作。如果您的应用程序将在多个 Web 服务器之间实现负载均衡,你应该选择一个所有服务器都可以访问的集中存储,比如 Redis 或数据库。

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

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

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

驱动先决条件

数据库

使用 database Session 驱动时,你需要创建一个记录 Session 的表。下面是 Schema 的声明示例:

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

你可以使用 session:table Artisan 命令生成这个迁移。了解更多数据库迁移,请查看完整的文档 迁移文档 :

php artisan session:table

php artisan migrate

Redis

在 Laravel 使用 Redis Session 驱动前,你需要安装 PhpRedis PHP 扩展,可以通过 PECL 或者 通过 Composer 安装这个 predis/predis 包 (~1.0)。
更多关于 Redis 配置信息, 查询 Laravel 的 Redis 文档 .

技巧:在 session 配置文件里, connection 选项可以用来设置 Session 使用 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 方法第二个参数里传递一个 default 默认值,如果 Session 里不存在键值对 key 的数据结果,这个默认值就会返回。如果你传递给 get 方法一个闭包作为默认值,这个闭包会被执行并且返回结果。

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

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

全局 Session 助手函数

你也可以在 Session 里 使用 PHP 全局 session 函数获取和储存数据。当这个 session 函数以一个单独的字符串形式被调用时,它将会返回这个 Session 键值对的结果。当函数以 key / value 数组形式被调用时,这些值会被存储在 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 方法。如果条目存在 has 方法返回 true 不存在则返回 null:

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

判断 Session 里是否存在一个即使结果值为 null 的条目,你可以使用 exists 方法:

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

存储数据

Session 里存储数据, 你通常将使用 Request 实例中的 put 方法 或者 session 助手函数:

// 通过 Request ...
$request->session()->put('key', 'value');

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

Session 存储数组

push 方法可以把一个新值推入到以数组形式存储的 session 值里。例如: 如果 user.teams 键值对有一个关于团队名字的数组,你可以推入一个新值到这个数组里:

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

获取 & 删除条目

pull 方法会从 Session 里获取并且删除一个条目,只需要一步如下 :

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

加减操作 Session 值

如果你的 Session 数据里有整形你希望进行加减操作, 可以使用 incrementdecrement 方法:

$request->session()->increment('count'); 

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

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

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

闪存数据

有时你可能想在 Session 里为下次请求存储一些条目。你可以使用 flash 方法。使用这个方法,存储在 Session 的数据将立即可用并且会保留到下一个 HTTP 请求期间,之后会被删除。闪存数据主要用于短期的状态消息:

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

如果你需要为多次请求持久化闪存数据,可以使用 reflash 方法,它会为一个额外的请求保持住所有的闪存数据,如果你仅需要保持特定的闪存数据,可以使用 keep 方法:

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

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

如果你仅为了当前的请求持久化闪存数据,可以使用 now 方法:

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

删除数据

forget 方法会从 Session 删除一些数据。如果你想删除所有 Session 数据, 可以使用 flush 方法:

// 删除一个单独的键值对 ...
$request->session()->forget('name');

// 删除多个 键值对 ...
$request->session()->forget(['name', 'status']);

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

重新生成 Session ID

重新生成 Session ID 经常被用来阻止恶意用户使用 Session Fixation 攻击你的应用。

如果你正在使用 入门套件 或者 Laravel Fortify 中的任意一种, Laravel 会在认证阶段自动生成 Session ID;然而如果你需要手动重新生成 Session ID ,可以使用 regenerate 方法:

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

如果你需要重新生成 Session ID 并同时删除所有 Session 里的数据,可以使用 invalidate 方法:

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

Session 阻塞

注意:应用 Session 阻塞功能,你的应用必须使用一个支持 原子锁 的缓存驱动。目前,可用的缓存驱动有 memcacheddynamodbredisdatabase 等。另外,你可能不会使用 cookie Session 驱动。

默认情况下,Laravel 允许使用同一 Session 的请求并发地执行,举例来说,如果你使用一个 JavaScript HTTP 库向你的应用执行两次 HTTP 请求,它们将同时执行。对多数应用这不是问题,然而 在一小部分应用中可能出现 Session 数据丢失,这些应用会向两个不同的应用端并发请求,并同时写入数据到 Session。

为了解决这个问题,Laravel 允许你限制指定 Session 的并发请求。首先,你可以在路由定义时使用block 链式方法。在这个示例中,一个到 /profile 的路由请求会拿到一把 Session 锁。当它处在锁定状态时,任何使用相同 Session ID 的到 /profile 或者/order 的路由请求都必须等待,直到第一个请求处理完成后再继续执行:

Route::post('/profile', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10)

Route::post('/order', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10)

block 方法接受两个可选参数。第一个参数是 Session 锁在释放之前应保持的最大秒数。当然,如果程序在最大值前已执行完成,锁就会提前释放。

第二个参数是尝试获得 Session 锁时等待的时间秒数。如果无法在设定的时间秒数内获得Session 锁,则抛出一个 Illuminate\Contracts\Cache\LockTimeoutException 异常。

如果不传参,那么 Session 锁默认锁定最大时间是10秒,请求锁最大的等待时间也是10秒:

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

添加自定义 Session 驱动

实现驱动

如果现存的 Session 驱动不能满足你的需求, Laravel 允许你自定义 Session Handler 。你的自定义驱动应实现 PHP 内置的 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 驱动。你无须在里面写任何代码。可以简单地忽略掉。
  • close 方法跟 open 方法很像,通常也可以忽略掉。对大多数驱动来说,它不是必须的。
  • read 方法应返回与给定的 $sessionId 关联的 Session 数据的字符串格式。在你的驱动中获取或存储 Session 数据时,无须作任何序列化和编码的操作,Laravel 会自动为你执行序列化。
  • write 方法将与 $sessionId 关联的给定的 $data 字符串写入到一些持久化存储系统,如 MongoDB 或者其他你选择的存储系统。再次,你无须进行任何序列化操作,Laravel 会自动为你处理。
  • destroy 方法应可以从持久化存储中删除与 $sessionId 相关联的数据。
  • gc 方法应可以销毁给定的 $lifetime( UNIX 时间戳格式 )之前的所有 Session 数据。对于像 Memcached 和 Redis 这类拥有过期机制的系统来说,本方法可以置空。

注册驱动

一旦你的驱动实现了,需要注册到 Laravel 。在 Laravel 中添加额外的驱动到 Session 后端 ,你可以使用 Session Facade 提供的 extend 方法。你应该在 服务提供者 中的 boot 方法中调用 extend 方法。可以通过已有的 App\Providers\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) {
            // 返回一个 SessionHandlerInterface 接口的实现 ...
            return new MongoSessionHandler;
        });
    }
}

一旦 Session 驱动注册完成, 就可以在 config/session.php 配置文件选择使用 mongo 驱动。

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

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
贡献者:4
讨论数量: 0
发起讨论 只看当前版本


暂无话题~