HTTP 会话
介绍
由于 HTTP 驱动的应用程序是无状态的,因此会话提供了一种跨多个请求存储用户信息的方法。该用户信息通常存储在持久化存储/后端中,以便后续请求可以访问。
Laravel 内置了多种会话后端,可通过统一且富有表现力的 API 进行访问。它支持Memcached、Redis和数据库等常用后端。
配置
应用程序的会话配置文件存储在config/session.php
。请务必查看此文件中可用的选项。默认情况下,Laravel 配置为使用database
会话驱动程序。
会话driver
配置选项定义了每个请求的会话数据的存储位置。Laravel 包含多种驱动程序:
file
- 会话存储在 中storage/framework/sessions
。cookie
- 会话存储在安全、加密的 cookie 中。database
- 会话存储在关系数据库中。memcached
/redis
- 会话存储在这些快速的、基于缓存的存储库之一中。dynamodb
- 会话存储在 AWS DynamoDB 中。array
- 会话存储在 PHP 数组中,不会持久保存。
数组驱动程序主要用于测试期间,并防止会话中存储的数据被持久化。
驱动程序先决条件
数据库
使用database
Session 驱动时,你需要确保有一个数据库表来保存 Session 数据。通常,这包含在 Laravel 的默认0001_01_01_000000_create_users_table.php
数据库迁移中;但是,如果你因为某种原因没有sessions
数据库表,可以使用make:session-table
Artisan 命令来生成此迁移:
1php artisan make:session-table2 3php artisan migrate
Redis
在 Laravel 中使用 Redis 会话之前,您需要通过 PECL 安装 PhpRedis PHP 扩展,或predis/predis
通过 Composer 安装该软件包 (~1.0)。有关配置 Redis 的更多信息,请参阅 Laravel 的Redis 文档。
环境SESSION_CONNECTION
变量或配置文件connection
中的选项session.php
可用于指定哪个 Redis 连接用于会话存储。
与会话交互
检索数据
在 Laravel 中,处理会话数据主要有两种方式:全局session
辅助方法和实例Request
。首先,我们来看看如何通过Request
实例访问会话,实例可以在路由闭包或控制器方法上进行类型Prompts。记住,控制器方法的依赖关系会通过 Laravel服务容器自动注入:
1<?php 2 3namespace App\Http\Controllers; 4 5use Illuminate\Http\Request; 6use Illuminate\View\View; 7 8class UserController extends Controller 9{10 /**11 * Show the profile for the given user.12 */13 public function show(Request $request, string $id): View14 {15 $value = $request->session()->get('key');16 17 // ...18 19 $user = $this->users->find($id);20 21 return view('user.profile', ['user' => $user]);22 }23}
从会话中检索项目时,还可以将默认值作为第二个参数传递给该get
方法。如果会话中不存在指定的键,则返回此默认值。如果将闭包作为默认值传递给该get
方法,并且请求的键不存在,则将执行该闭包并返回其结果:
1$value = $request->session()->get('key', 'default');2 3$value = $request->session()->get('key', function () {4 return 'default';5});
全局会话Helpers
您还可以使用全局session
PHP 函数在会话中检索和存储数据。当session
使用单个字符串参数调用此Helpers时,它将返回该会话键的值。当使用键/值对数组调用此Helpers时,这些值将存储在会话中:
1Route::get('/home', function () { 2 // Retrieve a piece of data from the session... 3 $value = session('key'); 4 5 // Specifying a default value... 6 $value = session('key', 'default'); 7 8 // Store a piece of data in the session... 9 session(['key' => 'value']);10});
通过 HTTP 请求实例使用会话与使用全局session
辅助方法之间几乎没有实际区别。这两种方法都可以通过所有测试用例中可用的方法来测试。assertSessionHas
检索所有会话数据
如果您想要检索会话中的所有数据,您可以使用该all
方法:
1$data = $request->session()->all();
检索部分会话数据
和方法可用于检索会话数据的子集only
:except
1$data = $request->session()->only(['username', 'email']);2 3$data = $request->session()->except(['username', 'email']);
确定会话中是否存在某个项目
要确定某个项目是否存在于会话中,可以使用该has
方法。如果项目存在,则该方法返回结果。如果项目不存在,has
则返回结果:true
null
1if ($request->session()->has('users')) {2 // ...3}
要确定会话中是否存在某个项目,即使其值为null
,您也可以使用该exists
方法:
1if ($request->session()->exists('users')) {2 // ...3}
要判断某个项目是否在 session 中不存在,可以使用该missing
方法。如果该项目不存在,该missing
方法将返回:true
1if ($request->session()->missing('users')) {2 // ...3}
存储数据
要在会话中存储数据,通常使用请求实例的put
方法或全局session
帮助程序:
1// Via a request instance...2$request->session()->put('key', 'value');3 4// Via the global "session" helper...5session(['key' => 'value']);
推送至数组会话值
该push
方法可用于将新值推送到作为数组的会话值。例如,如果user.teams
键包含一个球队名称数组,则可以像这样将新值推送到数组中:
1$request->session()->push('user.teams', 'developers');
检索和删除项目
该pull
方法将通过一条语句从会话中检索并删除一个项目:
1$value = $request->session()->pull('key', 'default');
增加和减少会话值
如果您的会话数据包含您希望增加或减少的整数,则可以使用increment
和decrement
方法:
1$request->session()->increment('count');2 3$request->session()->increment('count', $incrementBy = 2);4 5$request->session()->decrement('count');6 7$request->session()->decrement('count', $decrementBy = 2);
闪存数据
有时您可能希望将项目存储在会话中以供下次请求使用。您可以使用该flash
方法来实现。使用此方法存储在会话中的数据将立即可用,并在后续 HTTP 请求期间可用。在后续 HTTP 请求之后,闪存数据将被删除。闪存数据主要用于短期状态消息:
1$request->session()->flash('status', 'Task was successful!');
如果您需要为多个请求保留 Flash 数据,可以使用该reflash
方法,该方法将保留所有 Flash 数据以供下次请求使用。如果您只需要保留特定的 Flash 数据,可以使用该keep
方法:
1$request->session()->reflash();2 3$request->session()->keep(['username', 'email']);
要仅为当前请求保留闪存数据,您可以使用该now
方法:
1$request->session()->now('status', 'Task was successful!');
删除数据
该forget
方法将从会话中删除一条数据。如果您想从会话中删除所有数据,可以使用该flush
方法:
1// Forget a single key...2$request->session()->forget('name');3 4// Forget multiple keys...5$request->session()->forget(['name', 'status']);6 7$request->session()->flush();
重新生成会话 ID
重新生成会话 ID 通常是为了防止恶意用户利用会话固定攻击您的应用程序。
如果您使用 Laravel应用程序入门套件或Laravel Fortify之一,Laravel 会在身份验证期间自动重新生成会话 ID ;但是,如果您需要手动重新生成会话 ID,则可以使用该regenerate
方法:
1$request->session()->regenerate();
如果您需要重新生成会话 ID 并在单个语句中从会话中删除所有数据,则可以使用该invalidate
方法:
1$request->session()->invalidate();
会话阻止
要使用会话阻塞功能,您的应用程序必须使用支持原子锁的缓存驱动程序。目前,这些缓存驱动程序包括memcached
、dynamodb
、redis
(mongodb
包含在官方mongodb/laravel-mongodb
软件包中)、database
、file
和array
驱动程序。此外,您不能使用cookie
会话驱动程序。
默认情况下,Laravel 允许使用同一会话的请求并发执行。例如,如果您使用 JavaScript HTTP 库向应用程序发出两个 HTTP 请求,它们将同时执行。对于许多应用程序来说,这并非问题;然而,一小部分应用程序可能会发生会话数据丢失,因为它们会并发请求两个不同的应用程序端点,并将数据写入会话。
为了缓解这种情况,Laravel 提供了限制特定会话并发请求的功能。首先,你可以简单地将该block
方法链接到路由定义中。在本例中,传入到 端点的请求/profile
将获取会话锁。在持有此锁期间,任何传入到/profile
或/order
端点且共享相同会话 ID 的请求都将等待第一个请求执行完毕,然后才能继续执行:
1Route::post('/profile', function () {2 // ...3})->block($lockSeconds = 10, $waitSeconds = 10);4 5Route::post('/order', function () {6 // ...7})->block($lockSeconds = 10, $waitSeconds = 10);
该block
方法接受两个可选参数。第一个参数block
是会话锁在释放前应保持的最大秒数。当然,如果请求在此时间之前完成执行,锁将提前释放。
该方法接受的第二个参数block
是请求尝试获取会话锁时应等待的秒数。Illuminate\Contracts\Cache\LockTimeoutException
如果请求在给定的秒数内无法获取会话锁,则会抛出一个异常。
如果没有传递这两个参数,则最多可以获得 10 秒的锁,并且请求在尝试获取锁时最多等待 10 秒:
1Route::post('/profile', function () {2 // ...3})->block();
添加自定义会话驱动程序
实现驱动程序
如果现有的 Session 驱动都无法满足你的应用需求,Laravel 允许你编写自己的 Session 处理器。自定义 Session 驱动应该实现 PHP 内置的 接口SessionHandlerInterface
。该接口只包含几个简单的方法。一个 MongoDB 的存根实现如下所示:
1<?php 2 3namespace App\Extensions; 4 5class MongoSessionHandler implements \SessionHandlerInterface 6{ 7 public function open($savePath, $sessionName) {} 8 public function close() {} 9 public function read($sessionId) {}10 public function write($sessionId, $data) {}11 public function destroy($sessionId) {}12 public function gc($lifetime) {}13}
由于 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 的会话后端,你可以使用Facadeextend
提供的方法。你应该从服务提供者的方法中调用该方法。你可以从现有的提供者中执行此操作,也可以创建一个全新的提供者:Session
extend
boot
App\Providers\AppServiceProvider
1<?php 2 3namespace App\Providers; 4 5use App\Extensions\MongoSessionHandler; 6use Illuminate\Contracts\Foundation\Application; 7use Illuminate\Support\Facades\Session; 8use Illuminate\Support\ServiceProvider; 9 10class SessionServiceProvider extends ServiceProvider11{12 /**13 * Register any application services.14 */15 public function register(): void16 {17 // ...18 }19 20 /**21 * Bootstrap any application services.22 */23 public function boot(): void24 {25 Session::extend('mongo', function (Application $app) {26 // Return an implementation of SessionHandlerInterface...27 return new MongoSessionHandler;28 });29 }30}
一旦会话驱动程序被注册,您就可以使用环境变量或在应用程序的配置文件中mongo
将该驱动程序指定为应用程序的会话驱动程序。SESSION_DRIVER
config/session.php