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驱动程序。

查看笔记

扫码一下
查看教程更方便