HTTP Session
简介
由于支持HTTP协议的应用程序是无状态的,因此laravel session提供了一种在多个请求中存储相关用户信息的方法。Laravel附带了各种会话后端,它们可以通过一个表现性的统一API进行访问。像对流行的数据库或缓存的支持
配置
session
配置文件存储在config/session.php
。确保此文件中的选项可用。默认情况下,Laravel配置为使用file
存储session,这可以很好地工作。
session driver
配置选项定义了每个请求的会话数据将存储在何处。Laravel支持一些出色的驱动程序:
file
-会话存储在storage/framework/sessions
中。cookie
-会话存储在安全的加密cookie中。database
-会话存储在关系数据库中。memcached/ redis
-会话存储在这些快速,基于缓存的应用之一。array
-会话存储在PHP数组中,但是不会持久保存。
数组驱动程序在测试期间使用,可防止持久存储在会话中的数据。
驱动程序先决条件
数据库
使用database会话驱动程序时,我们将需要创建一个包含会话项的表。下面是使用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:tableArtisan
命令快速生成:
$ php artisan session:table
$ php artisan migrate
Redis
在Laravel使用Redis会话之前,我们需要通过PECL安装PhpRedis
PHP扩展或通过Composer安装predis/predis
package(〜1.0 )。有关配置Redis的更多信息,请参阅其 Laravel Redis文档 页面。
使用session
检索数据
在Laravel中使用会话数据主要有两种方法:
- 全局session帮助函数
- 通过Request实例
首先,让我们看一下通过Request实例访问会话的情况,该实例可以在控制器方法中使用类型限定。请记住,控制器方法依赖项是通过Laravel 服务容器自动注入的:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class UserController extends Controller
{
/**
* Show the profile for the given user.
*
* @param Request $request
* @param int $id
* @return Response
*/
public function show(Request $request, $id)
{
$value = $request->session()->get('key');
//
}
}
检索会话中数据时,我们还可以给get
方法传递第二个参数作为默认值。如果会话中不存在指定的key的值,则将返回此默认值。如果给get
方法的第二个参数传一个Closure,并且请求的键不存在,Closure则将执行并返回其结果:
$value = $request->session()->get('key', 'default');
$value = $request->session()->get('key', function () {
return 'default';
});
全局session 帮助函数
我们可以使用全局session 函数在会话中检索和存储数据。当session帮助函数只有一个字符串参数时,它将返回会话中该key的值。当session函数的参数是一个键/值对数组时,这些值将存储在会话中:
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']);
});
通过HTTP Request实例使用session与使用全局session帮助函数之间几乎没有实际区别。两种方法都可以在任何的测试用例中使用
assertSessionHas
方法进行测试。
检索所有session数据
如果要检索session中的所有数据,可以使用all方法:
$data = $request->session()->all();
确定会话中是否存在项目
要确定会话中是否存在某项,可以使用has方法。如果存在,并且其值不是null,则has方法返回true:
if ($request->session()->has('users')) {
//
}
has
方法是有缺陷的,对于值为null的项是不能检测出来的。这种情况,我们可以使用exists
方法。如果存在该项目,即使其值为null,exists方法也会返回true:
if ($request->session()->exists('users')) {
//
}
存储数据
要在会话中存储数据,通常使用put方法或session帮助函数:
// Via a request instance...
$request->session()->put('key', 'value');
// Via the global helper...
session(['key' => 'value']);
将数组作为session值
如果session值是一个数组,可以使用push
方法给该数组加入新的值。例如,如果user.teams是一个包含team名称的数组,则可以像这样将新值推送到该数组:
$request->session()->push('user.teams', 'developers');
检索和删除项目
pull
方法将在单个语句中检索并删除会话中的项目:
$value = $request->session()->pull('key', 'default');
闪存数据
有时我们可能希望在session中存储数据,仅为下一个请求可以使用。我们可以使用flash
方法进行此操作。使用此方法存储在会话中的数据是立即可用的,并且仅在下一个HTTP请求期间可用。在下一个HTTP请求之后,该数据将被删除。Flash数据主要用于短暂的状态消息:
$request->session()->flash('status', 'Task was successful!');
如果需要为几个请求保留Flash数据,则可以使用reflash
方法,该方法将保留所有Flash数据以供其他请求使用。如果只需要保留特定的闪存数据,则可以使用keep
方法:
$request->session()->reflash();
$request->session()->keep(['username', 'email']);
删除数据
forget
方法将从会话中删除一条数据。如果要从会话中删除所有数据,可以使用flush
方法:
// Forget a single key...
$request->session()->forget('key');
// Forget multiple keys...
$request->session()->forget(['key1', 'key2']);
$request->session()->flush();
重新生成会话ID
为了防止恶意用户利用应用程序上的会话固定攻击,通常我们会重新生成会话ID
如果使用内置方法LoginController
,Laravel会在身份验证过程中自动重新生成会话ID 。但是,如果需要手动重新生成会话ID,则可以使用regenerate
方法。
$request->session()->regenerate();
会话阻塞
默认情况下,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
方法接受的第一个参数是会话锁在释放之前应保持的最大秒数。当然,如果请求在此时间之前完成执行,则锁将提前释放。
block
方法接受的第二个参数是请求尝试获得会话锁时应等待的秒数。如果请求无法在给定的秒数内获得会话锁,则将会抛出一个Illuminate\Contracts\Cache\LockTimeoutException
异常。
如果没有传递这些参数,则将最多获得10秒的锁定,而等待时间10秒:
Route::post('/profile', function () {
//
})->block()
添加自定义会话驱动程序
实现驱动程序
我们的自定义会话驱动程序应实现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 的目录。我们可以随意将它们放置在喜欢的任何位置。在此示例中,我们创建了一个Extensions目录来存放
MongoSessionHandler
。
由于这些方法不容易理解,下面我们快速介绍这些方法的作用:
open
方法通常将在基于文件的会话存储系统中使用。由于Laravel附带了file会话驱动程序,因此我们几乎不需要在此方法中放入任何内容。您可以将其保留为空存根。接口设计不佳(我们将在后面讨论),PHP要求我们实现该接口的所有方法。close
方法与open方法一样,通常也可以忽略该方法。对于大多数驱动程序,不需要它。read
方法应返回与给定$sessionId
关联的会话数据的版本,该版本是一个字符串。在驱动程序中检索或存储会话数据时,无需进行任何序列化或其他编码,因为Laravel会为我们执行序列化。write
方法应将与给定$sessionId
关联的数据$data
存储到某些持久性存储系统,例如MongoDB,Dynamo等。同样,我们不应该执行任何序列化-Laravel已经为我们进行了序列化。destroy
方法应从该$sessionId
的持久性存储中删除与之关联的数据。gc
方法应销毁所有超过给定时间$lifetime
(UNIX时间戳)的会话数据。对于像Memcached和Redis这样的系统(存储到其中的数据本身就有到期时间),此方法可以保留为空。
注册驱动程序
一旦实现了驱动程序,就可以在框架中注册它了。要将其他驱动程序添加到Laravel的会话后端,可以使用Session Facade上的extend方法。我们应该从Service Provider的boot方法中调用该方法。我们可以从现有的 AppServiceProvider
中创建,也可以创建一个全新的Provider:
<?php
namespace App\Providers;
use App\Extensions\MongoSessionHandler;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
class SessionServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Session::extend('mongo', function ($app) {
// Return implementation of SessionHandlerInterface...
return new MongoSessionHandler;
});
}
}
一旦注册了会话驱动程序,就可以在config/session.php
配置文件中使用该mongo
驱动程序。