HTTP会话
Introduction
由于 HTTP 驱动的应用程序是无状态的,因此会话提供了一种跨多个请求存储有关用户信息的方法。该用户信息通常放置在可以从后续请求访问的持久存储/后端中。
Laravel 附带了多种会话后端,这些后端可以通过一个富有表现力的统一 API 进行访问。支持流行的后端,例如Memcached,Redis, 并且包含数据库。
Configuration
您的应用程序的会话配置文件存储在config/session.php
.请务必查看此文件中可用的选项。默认情况下,Laravel 配置为使用file
会话驱动程序,它将适用于许多应用程序。如果您的应用程序将在多个 Web 服务器之间进行负载平衡,您应该选择一个所有服务器都可以访问的集中存储,例如 Redis 或数据库。
会话driver
配置选项定义了为每个请求存储会话数据的位置。 Laravel 附带了几个开箱即用的优秀驱动程序:
-
file
- 会话存储在storage/framework/sessions
. -
cookie
- 会话存储在安全、加密的 cookie 中。 -
database
- 会话存储在关系数据库中。 -
memcached
/redis
- 会话存储在这些基于缓存的快速存储之一中。 -
dynamodb
- 会话存储在 AWS DynamoDB 中。 -
array
- 会话存储在 PHP 数组中,不会持久化。
Note
阵列驱动程序主要用于testing 并防止存储在会话中的数据被持久化。
驱动程序先决条件
Database
当使用database
会话驱动程序,您将需要创建一个表来包含会话记录。一个例子Schema
该表的声明可在下面找到:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::create('sessions', function (Blueprint $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 会话之前,您需要通过 PECL 安装 PhpRedis PHP 扩展或安装predis/predis
通过 Composer 打包 (~1.0)。有关配置 Redis 的更多信息,请参阅 Laravel 的Redis 文档.
Note
在里面session
配置文件,connection
选项可用于指定会话使用哪个 Redis 连接。
与会话交互
检索数据
在 Laravel 中有两种处理会话数据的主要方式:全局session
助手并通过Request
实例。首先,让我们看看通过Request
实例,它可以在路由闭包或控制器方法上进行类型提示。请记住,控制器方法依赖项是通过 Laravel 自动注入的服务容器:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*/
public function show(Request $request, string $id): View
{
$value = $request->session()->get('key');
// ...
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
当您从会话中检索项目时,您还可以将默认值作为第二个参数传递给get
方法。如果会话中不存在指定的键,将返回此默认值。如果将闭包作为默认值传递给get
方法并且请求的键不存在,将执行闭包并返回其结果:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
全局会话助手
您也可以使用全局session
用于在会话中检索和存储数据的 PHP 函数。当。。。的时候session
helper 使用单个字符串参数调用,它将返回该会话密钥的值。当使用键/值对数组调用助手时,这些值将存储在会话中:
Route::get('/home', function () {
// Retrieve a piece of data from the session...
$value = session('key');
// Specifying a default value...
$value = session('key', 'default');
// Store a piece of data in the session...
session(['key' => 'value']);
});
Note
通过 HTTP 请求实例使用会话与使用全局会话之间几乎没有实际区别session
帮手。两种方法都是testable 通过assertSessionHas
在所有测试用例中都可用的方法。
检索所有会话数据
如果您想检索会话中的所有数据,您可以使用all
方法:
$data = $request->session()->all();
确定项目是否存在于会话中
要确定某个项目是否存在于会话中,您可以使用has
方法。这has
方法返回true
如果该项目存在且不存在null
:
if ($request->session()->has('users')) {
// ...
}
确定一个项目是否存在于会话中,即使它的值是null
, 你可以使用exists
方法:
if ($request->session()->exists('users')) {
// ...
}
要确定某个项目是否不存在于会话中,您可以使用missing
方法。这missing
方法返回true
如果该项目不存在:
if ($request->session()->missing('users')) {
// ...
}
存储数据
要在会话中存储数据,您通常会使用请求实例的put
方法或全局session
帮手:
// Via a request instance...
$request->session()->put('key', 'value');
// Via the global "session" helper...
session(['key' => 'value']);
推送到数组会话值
这push
方法可用于将新值推送到作为数组的会话值上。例如,如果user.teams
key 包含一个团队名称数组,你可以像这样将一个新值推送到数组中:
$request->session()->push('user.teams', 'developers');
检索和删除项目
这pull
方法将在单个语句中从会话中检索和删除项目:
$value = $request->session()->pull('key', 'default');
递增和递减会话值
如果您的会话数据包含您希望递增或递减的整数,您可以使用increment
和decrement
方法:
$request->session()->increment('count');
$request->session()->increment('count', $incrementBy = 2);
$request->session()->decrement('count');
$request->session()->decrement('count', $decrementBy = 2);
闪存数据
有时您可能希望在会话中存储项目以供下一个请求使用。您可以使用flash
方法。使用此方法存储在会话中的数据将立即可用,并在随后的 HTTP 请求期间可用。在后续的 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
方法将从会话中删除一条数据。如果您想从会话中删除所有数据,您可以使用flush
方法:
// Forget a single key...
$request->session()->forget('name');
// Forget multiple keys...
$request->session()->forget(['name', 'status']);
$request->session()->flush();
重新生成会话 ID
重新生成会话 ID 通常是为了防止恶意用户利用会话固定 攻击你的应用程序。
如果您使用的是 Laravel 之一,Laravel 会在身份验证期间自动重新生成会话 ID应用入门套件 或者Laravel 强化;但是,如果您需要手动重新生成会话 ID,您可以使用regenerate
方法:
$request->session()->regenerate();
如果您需要在一条语句中重新生成会话 ID 并从会话中删除所有数据,您可以使用invalidate
方法:
$request->session()->invalidate();
会话阻塞
Warning
要使用会话阻塞,您的应用程序必须使用支持的缓存驱动程序原子锁.目前,这些缓存驱动程序包括memcached
,dynamodb
,redis
, 和database
司机。此外,您不得使用cookie
会话驱动程序。
默认情况下,Laravel 允许使用同一会话的请求并发执行。因此,例如,如果您使用 JavaScript HTTP 库向您的应用程序发出两个 HTTP 请求,它们将同时执行。对于许多应用程序,这不是问题;然而,会话数据丢失可能发生在一小部分应用程序中,这些应用程序向两个不同的应用程序端点发出并发请求,这两个应用程序端点都将数据写入会话。
为了缓解这种情况,Laravel 提供了允许您限制给定会话的并发请求的功能。要开始,您可以简单地将block
方法到您的路线定义。在这个例子中,传入请求到/profile
端点将获得会话锁。持有此锁时,任何传入的请求/profile
或者/order
共享相同会话 ID 的端点将等待第一个请求完成执行,然后再继续执行:
Route::post('/profile', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
Route::post('/order', function () {
// ...
})->block($lockSeconds = 10, $waitSeconds = 10)
这block
方法接受两个可选参数。接受的第一个论点block
method 是会话锁在释放之前应保持的最大秒数。当然,如果请求在这个时间之前执行完毕,锁会提前释放。
接受的第二个论点block
method 是请求在尝试获取会话锁时应等待的秒数。一个Illuminate\Contracts\Cache\LockTimeoutException
如果请求无法在给定的秒数内获得会话锁,将被抛出。
如果这些参数均未传递,则将获得最多 10 秒的锁,并且请求在尝试获得锁时将等待最多 10 秒:
Route::post('/profile', function () {
// ...
})->block()
添加自定义会话驱动程序
实施驱动程序
如果现有的会话驱动程序都不适合您的应用程序需求,Laravel 可以编写您自己的会话处理程序。您的自定义会话驱动程序应实现 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) {}
}
Note
Laravel 没有附带一个目录来包含你的扩展。您可以随意将它们放在任何您喜欢的地方。在这个例子中,我们创建了一个Extensions
存放的目录MongoSessionHandler
.
由于这些方法的目的不容易理解,让我们快速介绍一下每个方法的作用:
- 这
open
方法通常用于基于文件的会话存储系统。由于 Laravel 附带了file
会话驱动程序,您很少需要在此方法中放置任何内容。您可以简单地将此方法留空。 - 这
close
方法,比如open
方法,通常也可以忽略。对于大多数驱动程序,它不是必需的。 - 这
read
方法应返回与给定关联的会话数据的字符串版本$sessionId
.在驱动程序中检索或存储会话数据时无需进行任何序列化或其他编码,因为 Laravel 会为您执行序列化。 - 这
write
方法应该写给定的$data
与关联的字符串$sessionId
到一些持久性存储系统,例如 MongoDB 或您选择的其他存储系统。同样,你不应该执行任何序列化 - Laravel 已经为你处理了。 - 这
destroy
方法应该删除与$sessionId
来自持久存储。 - 这
gc
方法应该销毁所有早于给定的会话数据$lifetime
,这是一个 UNIX 时间戳。对于像 Memcached 和 Redis 这样的自过期系统,这个方法可以留空。
注册驱动程序
一旦你的驱动程序被实现,你就可以在 Laravel 中注册它了。要向 Laravel 的会话后端添加额外的驱动程序,你可以使用extend
提供的方法Session
facade.你应该打电话给extend
方法来自boot
一个方法服务提供者.您可以从现有的App\Providers\AppServiceProvider
或创建一个全新的提供者:
<?php
namespace App\Providers;
use App\Extensions\MongoSessionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
class SessionServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// ...
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Session::extend('mongo', function (Application $app) {
// Return an implementation of SessionHandlerInterface...
return new MongoSessionHandler;
});
}
}
注册会话驱动程序后,您可以使用mongo
你的司机config/session.php
配置文件。