Session
HTTP 会话机制#
简介#
由于 HTTP 驱动的应用程序是无状态的,Session 提供了一种在多个请求之间存储有关用户信息的方法,Laravel 通过同一个可读性强的 API 处理各种自带的后台驱动程序。支持诸如比较热门的 Memcached, Redis 和数据库。
配置#
Session 的配置文件存储在 config/session.php
文件中。请务必查看此文件中对于你而言可用的选项。默认情况下,Laravel 为绝大多数应用程序配置的 Session 驱动为 file
。在生产环境中,你可以考虑使用 memcached
或 redis
驱动,让 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 的缓存驱动。 当前,这些缓存驱动包括
memcached
,dynamodb
,redis
和database
驱动程序。 另外,您不能使用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
facade 的 extend
方法。你应该在 服务提供者 中的 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
驱动。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: