首页 > 后端开发 > php教程 > 深入研究拉拉维尔的会议

深入研究拉拉维尔的会议

Karen Carpenter
发布: 2025-03-06 02:32:13
原创
353 人浏览过

A Deep Dive into Sessions in Laravel

在构建 Laravel 应用程序时,几乎可以肯定您会在某些时候需要处理会话。它们是 Web 开发的基础部分。

本文将快速介绍会话是什么,它们如何在 Laravel 中工作,以及您如何在 Laravel 应用程序中使用它们。

然后,我们将更进一步,深入探讨如何使用“会话类”与会话交互,以避免我在处理 Laravel 应用程序时经常遇到的常见陷阱。

最后,我们将了解如何在 Laravel 中测试会话数据。

什么是会话?


默认情况下,Web 应用程序是无状态的,这意味着请求通常彼此不了解。因此,我们需要一种方法来存储请求之间的数 据。例如,当用户登录网站时,我们需要记住他们在访问期间已登录。这就是会话的用武之地。

简而言之,会话是一种在多个请求之间持久保存数据安全的方式。

会话数据可能用于存储以下内容:

  • 用户身份验证状态。
  • 可在另一页访问的临时数据。
  • 显示给用户的闪存消息。

会话数据可以存储在各种位置,例如:

  • Cookie
  • 数据库
  • 缓存存储(例如 Redis)

会话在 Laravel 中如何工作?


要了解会话是什么,让我们看看它们如何在 Laravel 中工作。

以下是您可能在 Laravel 应用程序的会话中找到的一些示例数据:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

让我们分解每个键可能代表的内容。

以下键由 Laravel 框架本身添加:

  • _token 值用于防止 CSRF 攻击。
  • _previous.url 值用于存储先前请求的 URL。
  • _flash.old 值用于存储先前请求中闪存会话数据的键。在本例中,它表示在先前请求中闪存了 success 值。
  • _flash.new 值用于存储当前请求中闪存会话数据的键。

以下键由我添加:

  • success 值用于存储可能显示给用户的成功消息。
  • current_team_id 值用于存储用户正在查看的当前团队的 ID。

默认情况下,Laravel 支持以下会话驱动程序:

  • cookie - 会话数据存储在安全且加密的 Cookie 中。
  • database - 会话存储在您的数据库中(例如 MySQL、PostgreSQL、SQLite)。
  • memcached / redis - 会话数据存储在这些快速的缓存存储中。
  • dynamodb - 会话数据存储在 AWS DynamoDB 中。
  • file - 会话数据存储在 storage/framework/sessions 中。
  • array - 会话数据存储在内存中的 PHP 数组中,不会持久保存。

其中一些驱动程序有设置要求。因此,在使用它们之前,务必检查 Laravel 文档以了解如何设置它们。

在 Laravel 中使用会话


Laravel 使使用会话变得非常简单。文档很好地解释了如何与会话交互。但是,让我们快速回顾一下基础知识。

对于我们的示例,我们将假设我们正在构建一个跨越多个页面的分步向导。我们将存储当前步骤以及每个步骤中输入的数据到会话中。这样,当用户完成所有步骤时,我们可以在向导结束时读取所有提交的数据。

为了使示例简单,我们还将使用 session() 辅助函数。但稍后我们将讨论使用 Session 门面或请求类访问会话数据。

# 从会话读取数据

要从会话读取数据,您可以使用 get 方法,如下所示:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

运行上述代码将返回为 wizard:current_step 键存储在会话中的值。如果该键在会话中没有存储的值,它将返回 null

此方法还允许您定义一个默认值,如果键不存在则返回:

<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

运行上述代码将返回为 wizard:current_step 键存储在会话中的值。如果该键在会话中没有存储的值,它将返回 1

还可能有一些时候您想从会话读取数据并同时将其删除(因此无法再次访问)。您可以为此使用 pull 函数:

<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

运行上述代码将返回为 wizard:current_step 键存储在会话中的值,然后将其从会话中删除。

# 将数据写入会话

要将数据写入会话,您可以使用 put 函数,如下所示:

<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

运行上述代码将把数组(在第二个参数中传递)存储为 wizard:step_one:form_data 键的值。

# 将数据推送到会话中的数组

类似地,您还可以使用 push 方法将数据推送到会话中的数组:

<code>session()->put(
    key: 'wizard:step_one:form_data',
    value: [
        'name' => 'Ash Allen',
        'email' => 'ash@example.com',
    ],
);</code>
登录后复制
登录后复制
登录后复制

假设 wizard:step_one:form_data:languages 键具有以下数据:

<code>session()->push(
    key: 'wizard:step_one:form_data:languages',
    value: 'javascript',
);</code>
登录后复制
登录后复制
登录后复制

上述代码(调用 push 方法)将更新会话值:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

如果 wizard:step_one:form_data:languages 值尚不存在于会话中,则使用 push 将创建会话键并将值设置为包含您传入的值的数组。

# 增加和减少会话中的数据

Laravel 还提供了一些方便的辅助方法,允许您增加和减少会话中的值:

您可以像这样增加会话中的值:

<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

当我们运行上面的代码时,如果 wizard:current_step 会话值为 3,它现在将增加到 4。

您还可以像这样减少会话中的值:

<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

如果值尚不存在于会话中,则将它们视为 0。因此,对空会话值调用 increment 将值设置为 1。对空会话值调用 decrement 将值设置为 -1。

这两种方法都允许您指定要增加或减少的数量:

<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

# 从会话中删除数据

您还可以使用 forget 方法从会话中删除数据:

<code>session()->put(
    key: 'wizard:step_one:form_data',
    value: [
        'name' => 'Ash Allen',
        'email' => 'ash@example.com',
    ],
);</code>
登录后复制
登录后复制
登录后复制

运行上述代码将从会话中删除属于 wizard:current_step 键的数据。

如果您想一次删除多个键,您可以将键数组传递给 forget 函数:

<code>session()->push(
    key: 'wizard:step_one:form_data:languages',
    value: 'javascript',
);</code>
登录后复制
登录后复制
登录后复制

或者,如果您希望从会话中删除所有数据,您可以使用 flush 函数:

<code>[
    `php`,
]</code>
登录后复制
登录后复制

# 检查会话中是否存在数据

Laravel 还提供了一些方便的辅助函数来检查会话中是否存在数据。

您可以使用 has 方法检查会话中是否存在键以及其值是否不是 null

<code>[
    `php`,
    `javascript`,
]</code>
登录后复制

如果值存在且不是 null,则上述代码将返回 true。如果值为 null 或键不存在,它将返回 false

类似地,您还可以使用 exists 方法检查会话中是否存在键(无论值是否为 null):

<code>session()->increment(key: 'wizard:current_step');</code>
登录后复制

您还可以检查会话中是否根本不存在会话:

<code>session()->decrement(key: 'wizard:current_step');</code>
登录后复制

# 将数据闪存到会话

可能有时您想在会话中持久保存一些数据,但仅限于下一个请求。例如,您可能希望在用户提交表单后向用户显示成功通知。

为此,您可以使用 flash 方法:

<code>session()->increment(key: 'wizard:current_step', amount: 2);
session()->decrement(key: 'wizard:current_step', amount: 2);</code>
登录后复制

如果您要运行上述代码,在下一次请求中,您可以从会话中读取该值(使用类似 session()->get('success') 的内容)进行显示。然后将其删除,以便在下一次请求中不可用。

可能有时您有一些闪存数据(在先前的请求中添加),并且您想将其保留到下一个请求。

您可以使用 reflash 方法刷新所有闪存数据:

<code>session()->forget(keys: 'wizard:current_step');</code>
登录后复制

或者,如果您只想保留一些闪存数据,您可以使用 keep 方法:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

运行上述代码将保留 successerror 闪存会话值,但会删除下一个请求的任何其他闪存数据。

辅助函数、门面或请求类?


到目前为止,我们只在示例中使用了 session() 辅助函数。

但您也可以使用 IlluminateSupportFacadesSession 门面或 IlluminateHttpRequest 类与会话交互。

无论您使用哪种方法,您仍然可以使用本文前面介绍的相同方法。这些方法只是与会话数据交互的不同方式。

要使用 Session 门面,您可以像这样调用方法:

<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

或者,您可以通过调用注入到控制器方法中的 IlluminateHttpRequest 实例上的 session 方法来访问会话。假设您有如下控制器方法:

<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

每种方法都是完全有效的,因此您可以决定您和您的团队更喜欢哪一种。

更进一步


对于较小的项目,使用我们前面讨论过的方法与会话交互是完全可以的。但是,随着 Laravel 项目的增长,您可能会遇到一些问题,这些问题会导致错误并使您的代码更难以维护。

因此,我们现在将介绍一些潜在的陷阱以及如何避免它们。

# 会话键中的错字

我看到的一个常见陷阱(我自己也经历过很多次)是会话键中的错字。

坚持使用我们的向导示例,假设我们要将当前步骤存储在会话中。因此,我们的代码可能如下所示:

<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

然后稍后,在代码库的不同部分,我们可能想从会话中读取当前步骤:

<code>session()->put(
    key: 'wizard:step_one:form_data',
    value: [
        'name' => 'Ash Allen',
        'email' => 'ash@example.com',
    ],
);</code>
登录后复制
登录后复制
登录后复制

您看到我刚才犯的错误了吗?我意外地尝试读取 wizard:step 键而不是 wizard:current_step 键。

这是一个简单的示例,但在大型代码库中,很容易犯这种错误。这些显而易见的错误也可能是最难发现的。

因此,避免这些错字的一种有用方法是使用常量或方法来生成会话键。

例如,如果会话键是静态的,您可以定义一个常量(可能在稍后我们将介绍的会话类中),如下所示:

<code>session()->push(
    key: 'wizard:step_one:form_data:languages',
    value: 'javascript',
);</code>
登录后复制
登录后复制
登录后复制

这意味着我们减少了在代码库中使用的原始字符串的数量,这有助于减少错字的数量。

但是,有时您可能需要动态生成会话键。例如,假设我们希望我们的 wizard:current_step 键包含一个团队 ID 字段。我们可以创建一个方法来生成密钥,如下所示:

<code>[
    `php`,
]</code>
登录后复制
登录后复制

正如我们在上面的代码中看到的,我们正在动态生成会话键,以便它可以在不同的方法中使用。例如,如果我们试图查找 ID 为 1 的团队的当前步骤,则密钥将是 wizard:1:current_step

# 会话键冲突

我在处理存在一段时间的项目时看到的另一个陷阱是会话键冲突。

例如,想象一下,几年前您构建了一个用于创建新用户帐户的向导。因此,您可能像这样存储会话数据:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

现在,您已被分配构建一个也具有向导的新功能,并且您完全忘记了旧的向导和您使用的命名约定。您可能会意外地为新向导使用相同的键,导致数据冲突并引入潜在的错误。

为了避免这种情况,我喜欢用功能名称作为会话键的前缀。因此,对于保存用于创建新用户的向导数据,我可能有以下键:

  • new_user_wizard:current_step
  • new_user_wizard:step_one:form_data
  • new_user_wizard:step_two:form_data
  • 等等...

然后在我的用于创建新团队的新向导中,我可能有以下键:

  • new_team_wizard:current_step
  • new_team_wizard:step_one:form_data
  • new_team_wizard:step_two:form_data
  • 等等...

我们将在本文后面介绍如何在会话类中添加这些前缀。

# 未知数据类型

你能告诉我此会话值中存储的数据类型是什么吗?

<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

如果您猜到它是一个 AppDataTransferObjectsWizardsFormData 实例,那么您是对的。

说笑归说笑,我想说明的是,当您从会话中读取数据时,并不总是立即清楚您正在使用什么类型的数据。您最终必须查看将数据写入会话的代码才能弄清楚它是什么。这可能会分散注意力且耗时,并可能导致错误。

您可以向读取会话数据的代码添加注释或文档块。但这只是一个提示。如果注释没有保持最新(如果会话数据类型发生更改),那么它就没有帮助,并且会增加错误的可能性。

我喜欢使用的另一种方法是在方法内部读取会话数据并向该方法添加返回类型。这样,您可以确保您正在使用的数据类型正确。它还有助于您的 IDE 和阅读代码的人员。

例如,让我们来看这段代码:

<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们现在可以立即看到 stepOneFormData 方法返回一个 AppDataTransferObjectsWizardsFormData 实例。这清楚地说明了我们正在使用的数据类型。然后,我们可以在控制器中像这样调用此方法:

<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

# 在会话类中处理会话数据

正如我们在前面几节中看到的,在 Laravel 中使用会话时,有一些很容易避免(但很常见)的陷阱。

通过使用“会话类”,可以避免(或至少减少)这些陷阱中的每一个。我喜欢使用会话类来在一个地方封装与单个功能相关的会话数据处理逻辑。

例如,假设我们有一个用于创建用户和另一个用于创建团队的向导。我将为这些向导中的每一个创建一个会话类:

  • AppSessionsUsersNewUserWizardSession
  • AppSessionsTeamsNewTeamWizardSession

通过使用会话类,您可以:

  • 自动使用功能名称作为所有键的前缀。
  • 向方法添加类型提示和返回类型。
  • 减少代码库中使用的原始字符串的数量。
  • 使重构会话数据结构更容易。
  • 使测试会话数据更容易。
  • 确切地知道如果需要对给定功能的会话数据进行任何更改,该去哪里。

在处理会话数据时使用这种基于类的方法在处理大型 Laravel 项目时为我节省了无数次时间。这是一种简单的方法,可以产生很大的影响。

在前面的示例中,我已经暗示过使用会话类。但是,让我们更深入地了解我喜欢如何构建这些类。

假设我们有以下用于新用户向导的会话类。乍一看它可能有点让人不知所措,但让我们看看代码,然后分解它:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

在上面的 AppSessionsUsersWizardSession 类中,我们首先定义了接受 IlluminateContractsSessionSession 实例的构造函数。通过这样做,当我们从服务容器中解析 AppSessionsUsersWizardSession 类时,Laravel 将自动为我们注入会话实例。我将稍后向您展示如何在控制器中执行此操作。

然后,我们定义了 5 个基本的公共方法:

  • getCurrentStep - 返回向导中的当前步骤。如果没有设置步骤,则默认为 1
  • setCurrentStep - 设置向导中的当前步骤。
  • setFormDataForStep - 设置向导中给定步骤的表单数据。此方法采用步骤号和 AppDataTransferObjectsWizardsUsersFormData 实例。
  • getFormDataForStep - 获取向导中给定步骤的表单数据。此方法采用步骤号并返回 AppDataTransferObjectsWizardsUsersFormData 实例或如果数据不存在则返回 null
  • flush - 从会话中删除与向导相关的所有数据。如果已完成或取消向导,您可能希望调用此方法。

您可能已经注意到所有键都在方法内部生成。我喜欢这样做是为了减少使用的原始字符串的数量(并减少错字的机会)。这也意味着如果我们想添加另一个访问特定键的方法,我们可以很容易地做到这一点。

使用这些键生成方法的额外好处是我们可以轻松地向键添加前缀以避免冲突。在此示例中,我们通过使用 sessionKey 方法将所有键的前缀设置为 new_user_wizard:

现在此类已设置,让我们看看我们如何在控制器中与它交互:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

正如我们看到的,在上面的示例中,我们将 AppSessionsUsersWizardSession 类注入到我们的控制器方法中。Laravel 将自动为我们解析会话实例。

然后,我们可以像使用任何其他类一样与它交互。

起初,这可能感觉像过度抽象和需要维护更多代码。但是,随着项目的增长,类型提示、返回类型、键生成方法,甚至方法的命名(使您的操作更具描述性)都非常有用。

在 Laravel 中测试会话


就像代码库的任何其他部分一样,您应该确保您拥有会话数据的覆盖范围,以确保正在读取和写入正确的字段。

使用会话类的一大好处是您可以轻松地为类中的每个方法编写集中的单元样式测试。

例如,我们可以为 AppSessionsUsersWizardSession 类的 getFormDataForStep 方法编写一些测试。作为提醒,以下是该方法:

<code>$currentStep = session()->get(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

我们可以在这里测试几个场景:

  • 为步骤返回 AppDataTransferObjectsWizardsUsersFormData 对象。
  • 如果步骤不存在表单数据,则返回 null

我们的测试类可能如下所示:

<code>$currentStep = session()->get(key: 'wizard:current_step', default: 1);</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

在上面的测试类中,我们有两个测试涵盖了我们前面提到的两种情况。

这些单元样式测试非常适合确保您的会话类配置正确以读取和写入会话数据。但它们并不一定能让你相信它们在代码库的其余部分被正确使用。例如,您可能正在调用 getFormDataForStep(1),而您应该调用 getFormDataForStep(2)

出于这个原因,您可能还想考虑在您的功能测试(您通常为控制器编写的测试)中断言会话数据。

例如,假设您的控制器中有以下基本方法,它会转到向导中的下一步:

<code>$currentStep = session()->pull(key: 'wizard:current_step');</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

在上面的方法中,我们首先从会话中读取当前步骤。然后,我们将当前步骤的表单数据存储在会话中。最后,我们递增当前步骤并重定向到向导中的下一步。

我们将假设我们的 AppHttpRequestsUsersWizardNextStepRequest 类负责验证表单数据,并在我们调用 toDto 方法时返回 AppDataTransferObjectsWizardsUsersFormData 实例。

我们还将假设 nextStep 控制器方法可通过对 /users/wizard/next-step 路由(名为 users.wizard.next-step)的 POST 请求访问。

我们可能想编写如下测试以确保表单数据被正确存储在会话中:

<code>[
  '_token' => 'bKmSfoegonZLeIe8B6TWvSm1dKwftKsvcT40xaaW'
  '_previous' => [
    'url' => 'https://my-app.com/users'
  ]
  '_flash' => [
    'old' => [
        'success',
    ],
    'new' => []
  ]
  'success' => 'User created successfully.'
  'current_team_id' => 123
]</code>
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制

在上面的测试中,我们正在使用一些表单数据向 /users/wizard/next-step 路由发出 POST 请求。您可能会注意到我们正在使用 withSession。此方法允许我们设置会话数据,以便我们可以断言它被正确读取。

然后,我们断言用户被重定向到向导中的下一步,并且会话中的当前步骤设置为 3。我们还断言步骤 2 的表单数据被正确存储在会话中。

正如我们在测试中看到的,我们还通过两种方式从会话中读取:

  • 使用 assertSessionHas 方法检查会话数据是否正确设置。
  • 使用 session() 辅助函数直接读取会话数据。

这两种方法都是有效的,因此您可以决定更喜欢哪一种。我在上面的测试中使用了这两种方法来向您展示您有多种选择。

结论


希望本文能帮助您很好地理解会话是什么以及它们如何在 Laravel 中工作。我还希望它们能为您提供一些关于如何使用基于类的方法与会话数据交互以避免一些常见陷阱的想法。

以上是深入研究拉拉维尔的会议的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
作者最新文章
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板