Laravel 验证终极指南

2024 年 6 月 3 日

验证是任何 Web 应用程序的重要组成部分。它可以帮助防止安全漏洞、数据损坏以及在处理用户输入时可能出现的一系列其他问题。

在本文中,我们将了解什么是验证以及它为何如此重要。然后,我们将比较客户端验证与服务器端验证,并探讨为什么您永远不应该在应用程序中仅依赖客户端验证。

然后我们将了解一些我喜欢在 Laravel 应用程序中使用的方便的验证规则。最后,我们将了解如何创建自己的验证规则并对其进行测试以确保其按预期工作。

什么是验证?

验证是在尝试使用数据之前检查数据是否有效的过程。这可以是任何事情,从检查简单的事情(例如请求中是否存在必填字段)到更复杂的检查(例如字段是否与特定模式匹配或在数据库中是否唯一)。

通常,在验证 Web 应用程序中的数据时,如果数据无效,您需要向用户返回错误消息。

这有助于防止安全漏洞、数据损坏并提高数据准确性。因此,只有在数据有效的情况下,我们才会继续处理请求。

请记住,来自用户的任何数据都不可信(至少在您验证之前)!

为什么验证很重要?

验证很重要,原因有很多,包括:

提高安全性

验证应用程序中的数据的最重要原因之一是提高安全性。通过在使用数据之前对其进行验证,您可以减少使用任何恶意输入来攻击您的应用程序或用户的机会。

防止存储错误数据

想象一个场景,我们期望一个字段是一个整数,但用户却传递一个文件。当我们尝试在应用程序的其他地方使用该数据时,这可能会导致应用程序出现各种问题。

再举一个例子,假设您正在构建一个允许用户对民意调查进行投票的 Web 应用程序。民意调查只能在 opens_at 时间和一个 closes_at 指定的时间 App\Models\Poll 模型。如果设置投票的人不小心设置了投票,会发生什么情况 closes_at 时间之前 opens_at 时间?根据您在应用程序中处理此问题的方式,这可能会导致各种问题。

通过在将数据存储到模型之前验证数据,我们可以提高应用程序中的数据准确性并减少存储错误数据的机会。

确保正确的 Artisan 命令输入

除了能够验证 HTTP 请求中传递的数据之外,您还可以验证 Artisan 命令。这有助于防止开发人员意外输入无效值并导致应用程序出现问题。

客户端验证与服务器端验证

通常可以在应用程序中使用两种类型的验证:客户端验证和服务器端验证。

客户端验证

客户端验证是在数据发送到服务器之前在浏览器中执行的验证。它可以使用 JavaScript 甚至使用 HTML 属性来实现。

例如,我们可以向 HTML 中的数字字段添加一些简单的验证,以确保用户输入 1 到 10 之间的数字:

<input type="number" name="quantity" min="1" max="10" required>

此输入字段有四个单独的部分,可用于客户端验证目的:

  • type="number" :这告诉浏览器输入应该是数字。在大多数浏览器上,这将阻止用户输入数字以外的任何内容。在移动设备上,它甚至可能会显示数字键盘而不是常规键盘,这对于用户体验来说非常好。
  • min="1" :这告诉浏览器输入的数字必须至少为 1。
  • max="10" :这告诉浏览器输入的数字最多只能是 10。
  • required :这告诉浏览器该字段是必填字段,并且必须在提交表单之前填写。

在大多数浏览器中,如果用户尝试提交具有无效值(或根本没有值)的表单,浏览器将阻止提交表单并向用户显示错误消息或提示。

这对于指导用户和改善应用程序的一般用户体验非常有用。但这就是所有这些应该被视为:指南。您永远不应该依赖客户端验证作为应用程序中唯一的验证形式。

如果有人在浏览器中打开开发人员工具,他们可以轻松删除并绕过您已有的客户端验证。

除此之外,重要的是要记住,当恶意用户试图攻击您的应用程序时,他们通常会使用自动脚本将请求直接发送到您的服务器。这意味着您现有的客户端验证将被绕过。

服务器端验证

服务器端验证是在服务器上的应用程序后端运行的验证。在 Laravel 应用程序的上下文中,这通常是您在控制器或表单请求类中运行的验证。

由于验证位于您的服务器上并且用户无法更改,因此这是真正确保发送到服务器的数据有效的唯一方法。

因此,在应用程序中始终保持服务器端验证非常重要。在理想的情况下,您尝试从请求中读取的每个字段都应该在您尝试使用它来执行任何业务逻辑之前进行验证。

Laravel 如何处理验证

现在我们已经了解了什么是验证以及为什么它很重要,让我们看看如何在 Laravel 中使用它。

如果您已经使用 Laravel 一段时间,您就会知道 Laravel 框架中内置了一个令人惊叹的验证系统。因此,在应用程序中开始验证非常容易。

在 Laravel 中验证数据有几种常见的方法,但我们将看看两种最常见的方法:

  • 手动验证数据
  • 使用表单请求类验证数据

手动验证数据

要手动验证数据(例如在控制器方法中),您可以使用 Illuminate\Support\Facades\Validator 门面并调用 make 方法。

然后我们可以将两个参数传递给 make 方法:

  • data - 我们要验证的数据
  • rules - 我们想要验证数据的规则

旁注: make 方法还接受两个可选参数: messagesattributes 。这些可用于自定义返回给用户的错误消息,但我们不会在本文中介绍它们。

让我们看一个示例,了解您可能希望如何验证两个字段:

use Illuminate\Support\Facades\Validator;

$validator = Validator::make(
    data: [
        'title' => 'Blog Post',
        'description' => 'Blog post description',
    ],
    rules: [
        'title' => ['required', 'string', 'max:100'],
        'description' => ['required', 'string', 'max:250'],
    ]
);

我们可以在上面的示例中看到我们正在验证两个字段: titlebody 。我们对这两个字段的值进行了硬编码,以使示例更加清晰,但在现实项目中,您通常会从请求中获取这些字段。我们正在检查 title field 已设置,为字符串,最大长度为 100 个字符。我们还在检查 description field 已设置,为字符串,最大长度为 250 个字符。

创建验证器后,我们可以调用验证器上的方法 Illuminate\Validation\Validator 返回的实例。例如,要检查验证是否失败,我们可以调用 fails 方法:

$validator = Validator::make(
    data: [
        'title' => 'Blog Post',
        'description' => 'Blog post description',
    ],
    rules: [
        'title' => ['required', 'string', 'max:100'],
        'description' => ['required', 'string', 'max:250'],
    ]
);

if ($validator->fails()) {
    // One or more of the fields failed validation.
    // Handle it here...
}

同样,我们也可以调用 validate 验证器实例上的方法:

Validator::make(
    data: [
        'title' => 'Blog Post',
        'description' => 'Blog post description',
    ],
    rules: [
        'title' => ['required', 'string', 'max:100'],
        'description' => ['required', 'string', 'max:250'],
    ]
)->validate();

validate 方法将 Illuminate\Validation\ValidationException 如果验证失败。 Laravel 将根据发出的请求类型自动处理此异常(假设您没有更改应用程序中的默认异常处理)。如果请求是 Web 请求,Laravel 会将用户重定向回上一页,并在会话中显示错误。如果请求是 API 请求,Laravel 将返回一个 422 Unprocessable Entity 使用验证错误的 JSON 表示形式进行响应,如下所示:

{
  "message": "The title field is required. (and 1 more error)",
  "errors": {
    "title": [
      "The title field is required."
    ],
    "description": [
      "The description field is required."
    ]
  }
}

使用表单请求类验证数据

通常验证 Laravel 应用程序中的数据的另一种方法是使用 表单请求类 。表单请求类是扩展的类 Illuminate\Foundation\Http\FormRequest 并用于对传入请求运行授权检查和验证。

我发现它们是保持控制器方法干净的好方法,因为 Laravel 会在运行控制器方法的代码之前自动对请求中传递的数据运行验证。因此我们不需要记住自己在验证器实例上运行任何方法。

让我们看一个简单的例子。想象一下我们有一个基本的 App\Http\Controllers\UserController 控制器与一个 store 允许我们创建新用户的方法:

declare(strict_types=1);

namespace App\Http\Controllers;

use App\Http\Requests\Users\StoreUserRequest;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Hash;

final class UserController extends Controller
{
    public function store(StoreUserRequest $request): RedirectResponse
    {
        User::create([
            'name' => $request->validated('name'),
            'email' => $request->validated('email'),
            'password' => Hash::make($request->validated('password')),
        ]);

        return redirect()
            ->route('users.index')
            ->with('success', 'User created successfully.');
    }
}

在控制器方法中,我们可以看到我们正在接受一个 App\Http\Requests\Users\StoreUserRequest 表单请求类(我们接下来会看到)作为方法参数。这将向 Laravel 表明我们希望在通过 HTTP 请求调用此方法时自动运行此请求类中的验证。

然后我们使用 validated 我们的控制器方法中的请求实例上的方法,用于从请求中获取经过验证的数据。这意味着它只会返回已验证的数据。例如,如果我们要尝试保存一个新的 profile_picture 控制器中的字段,它还必须添加到表单请求类中。否则不会被退回 validated 方法等等 $request->validated('profile_picture') 会回来 null

现在让我们来看看 App\Http\Requests\Users\StoreUserRequest 表单请求类:

declare(strict_types=1);

namespace App\Http\Requests\Users;

use App\Models\User;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;

final class StoreUserRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return $this->user()->can('create', User::class);
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:100'],
            'email' => ['required', 'email', Rule::unique('users')],
            'password' => [Password::defaults()],
        ];
    }
}

我们可以看到请求类包含两个方法:

  • authorize :该方法用于判断用户是否有权发出请求。如果该方法返回 false , A 403 Forbidden 响应将返回给用户。如果该方法返回 true ,将运行验证规则。
  • rules :此方法用于定义应对请求运行的验证规则。该方法应返回应在请求上运行的规则数组。

在里面 rules 方法,我们指定 name 必须设置字段,必须是字符串,并且最大长度必须为 100 个字符。我们还指定 email 字段必须设置,必须是电子邮件,并且在该字段中必须是唯一的 users 表(在 email 柱子)。最后,我们指定 password 字段必须设置,并且必须通过我们设置的默认密码验证规则(稍后我们将讨论密码验证)。

正如您所看到的,这是将验证逻辑与控制器逻辑分离的好方法,我发现它使代码更易于阅读和维护。

Laravel 中方便的验证规则

正如我已经提到的,Laravel 验证系统非常强大,使向应用程序添加验证变得轻而易举。

在本节中,我们将快速浏览一些我喜欢的方便的验证规则,并且认为大多数人会发现它们在您的应用程序中很有用。

如果您有兴趣查看 Laravel 中可用的所有规则,可以在 Laravel 文档中找到它们: https://laravel.com/docs/11.x/validation

验证数组

您需要运行的一种常见验证类型是验证数组。这可以是任何内容,从验证传递的 ID 数组是否都有效,到验证请求中传递的对象数组是否都具有某些字段。

让我们看一个如何验证数组的示例,然后我们将讨论正在做什么:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make(
    data: [
        'users' => [
            [
                'name' => 'Eric Barnes',
                'email' => 'eric@example.com',
            ],
            [
                'name' => 'Paul Redmond',
                'email' => 'paul@example.com',
            ],
            [
                'name' => 'Ash Allen',
                'email' => 'ash@example.com',
            ],
        ],
    ],
    rules: [
        'users' => ['required', 'array'],
        'users.*' => ['required', 'array:name,email'],
        'users.*.name' => ['required', 'string', 'max:100'],
        'users.*.email' => ['required', 'email', 'unique:users,email'],
    ]
);

在上面的示例中,我们传递了一个对象数组,每个对象都有一个 nameemail 场地。

为了验证,我们首先定义 users field 已设置并且是一个数组。然后我们指定数组的每个项目(目标使用 users.* ) 是一个数组,包含 nameemail 字段。

然后我们指定 name 字段(有针对性地使用 users.*.name ) 必须设置,必须是字符串,且不能超过 100 个字符。我们还指定 email 字段(有针对性地使用 users.*.email ) 必须设置,必须是电子邮件,并且在 users 表上的 email 柱子。

通过能够使用 * 验证规则中的通配符,我们可以轻松验证应用程序中的数据数组。

验证日期

Laravel 提供了一些方便的日期验证规则供您使用。首先,要验证字段是否为有效日期,您可以使用 date 规则:

$validator = Validator::make(
    data: [
        'opens_at' => '2024-04-25',
    ],
    rules: [
        'opens_at' => ['required', 'date'],
    ]
);

如果您想检查日期是否采用特定格式,可以使用 date_format 规则:

$validator = Validator::make(
    data: [
        'opens_at' => '2024-04-25',
    ],
    rules: [
        'opens_at' => ['required', 'date_format:Y-m-d'],
    ]
);

您可能想要检查一个日期是否在另一个日期之前或之后。例如,假设您有 opens_atcloses_at 您请求中的字段,并且您希望确保 closes_at 是在之后 opens_at 然后 opens_at 晚于或等于今天。您可以使用 after 像这样规则:

$validator = Validator::make(
    data: [
        'opens_at' => '2024-04-25',
        'closes_at' => '2024-04-26',
    ],
    rules: [
        'opens_at' => ['required', 'date', 'after:today'],
        'closes_at' => ['required', 'date', 'after_or_equal:opens_at'],
    ]
);

在上面的例子中,我们可以看到我们已经通过了 today 作为一个论据 after 规则为 opens_at 场地。 Laravel 将尝试将此字符串转换为有效的 DateTime 对象使用 strtotime 函数并将其与该函数进行比较。

为了 closes_at 田野,我们已经过去了 opens_at 作为一个论据 after_or_equal 规则。 Laravel 会自动检测这是另一个正在验证的字段,并将这两个字段相互比较。

同样,Laravel 还提供了 beforebefore_or_equal 可用于检查一个日期是否早于另一个日期的规则:

$validator = Validator::make(
    data: [
        'opens_at' => '2024-04-25',
        'closes_at' => '2024-04-26',
    ],
    rules: [
        'opens_at' => ['required', 'date', 'before:closes_at'],
        'closes_at' => ['required', 'date', 'before_or_equal:2024-04-27'],
    ]
);

验证密码

作为网络开发人员,我们的工作就是努力帮助我们的用户保持在线安全。我们可以做到这一点的一种方法是尝试在我们的应用程序中推广良好的密码实践,例如要求密码具有一定的长度、包含某些字符等。

Laravel 通过提供一个 Illuminate\Validation\Rules\Password 我们可以用来验证密码的类。

它提供了多种方法,我们可以将它们链接在一起来构建我们想要的密码验证规则。例如,假设我们希望用户的密码符合以下标准:

  • 长度至少为 8 个字符
  • 至少包含一个字母
  • 至少包含一个大写字母和一个小写字母
  • 至少包含一个数字
  • 至少包含一个符号
  • 密码不被泄露(即不在 Have I Been Pwned 数据库中,该数据库记录了因其他系统中的数据泄露而暴露的密码)

我们的验证可能看起来像这样:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Password;
 
$validator = Validator::make(
    data: [
        'password' => 'my-password-here'
        'password_confirmation' => 'my-password-here',
    ],
    rules: [
        'password' => [
            'required',
            'confirmed',
            Password::min(8)
                ->letters()
                ->mixedCase()
                ->numbers()
                ->symbols()
                ->uncompromised(),
        ],
    ],
);

正如我们在示例中看到的,我们使用可链接的方法来构建我们想要的密码验证规则。但是,如果我们在多个不同的地方使用这些规则(例如注册、重置密码、更新帐户页面上的密码等)并且我们需要更改此验证以强制执行至少 12 个字符,会发生什么情况?我们需要检查所有使用这些规则的地方并更新它们。

为了使这更容易,Laravel 允许我们定义一组默认的密码验证规则,我们可以在整个应用程序中使用它们。我们可以通过在中定义一组默认规则来做到这一点 boot 我们的方法 App\Providers\AppServiceProvider 像这样使用 Password::defaults() 方法:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;

class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Password::defaults(static function (): Password {
            return Password::min(8)
                ->letters()
                ->mixedCase()
                ->numbers()
                ->symbols()
                ->uncompromised();
        });
    }
}

完成此操作后,我们现在可以调用 Password::defaults() 在我们的验证规则和我们在 AppServiceProvider 将会被使用:

'password' => ['required', 'confirmed', Password::defaults()],

验证颜色

我参与过的几乎每个项目都包含某种形式的颜色选择器。无论是用户为自己的个人资料选择颜色、页面某个部分的背景颜色还是其他内容,这种情况都会经常出现。

在过去,我不得不使用正则表达式(我承认我并没有真正理解很多)来验证颜色是否是十六进制格式的有效颜色(例如 - #FF00FF )。然而,Laravel 现在有一个方便的 hex_color 您可以使用:

use Illuminate\Support\Facades\Validator;

Validator::make(
    data: [
        'color' => '#FF00FF',
    ],
    rules: [
        'color' => ['required', 'hex_color'],
    ]
);

验证文件

如果您通过服务器将文件上传到应用程序,则需要在尝试存储文件之前验证该文件是否有效。正如您所想象的,Laravel 提供了多种您可以使用的文件验证规则。

假设您希望允许用户上传 PDF (.pdf) 或 Microsoft Word (.docx) 文件。验证可能看起来像这样:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;

Validator::validate($request->all(), [
    'document' => [
        'required',
        File::types(['pdf', 'docx'])
            ->min('1kb')
            ->max('10mb'),
    ],
]);

在代码示例中,我们可以看到我们正在验证文件类型,并设置一些最小和最大文件大小限制。我们正在使用 types 方法来指定我们想要允许的文件类型。

minmax 方法还可以接受带有其他后缀的字符串,表示文件大小单位。例如,我们还可以使用:

  • 10kb
  • 10mb
  • 10gb
  • 10tb

此外,我们还能够使用以下方法确保文件是图像 image 方法上的 Illuminate\Validation\Rules\File 班级:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'photo' => [
        'required',
        File::image()
            ->min('1kb')
            ->max('10mb')
            ->dimensions(Rule::dimensions()->maxWidth(500)->maxHeight(500)),
    ],
]);

在上面的示例中,我们验证文件是否为图像,设置一些最小和最大文件大小限制,并设置一些最大尺寸 (500 x 500px)。

您可能希望在应用程序中采用不同的方法上传文件。例如,您可能希望直接从用户浏览器上传到云存储(例如 S3)。如果您愿意这样做,您可能想查看我的 使用 FilePond 在 Laravel 中上传文件 文章向您展示了如何执行此操作、您可能想要采用的不同验证方法以及如何测试它。

验证数据库中是否存在字段

您可能想要进行的另一个常见检查是确保数据库中存在某个值。

例如,假设您的应用程序中有一些用户,并且您已经创建了一条路由,以便可以将他们批量分配给一个团队。因此,在您的请求中,您可能需要验证 user_ids 请求中传递的所有内容都存在于 users 桌子。

为此,您可以使用 exists 规则并传递您要检查值是否存在于的表名称:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make(
    data: [
        'users_ids' => [
            111,
            222,
            333,
        ],
    ],
    rules: [
        'user_ids' => ['required', 'array'],
        'user_ids.*' => ['required', 'exists:users,id'],
    ]
);

在上面的示例中,我们检查传入的每个 ID user_ids 数组存在于 users 表上的 id 柱子。

这是在尝试使用数据之前确保您正在使用的数据有效且存在于数据库中的好方法。

如果您想更进一步,您可以应用 where 条款至 exists 规则来进一步过滤运行的查询:

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make(
    data: [
        'users_ids' => [
            111,
            222,
            333,
        ],
    ],
    rules: [
        'user_ids' => ['required', 'array'],
        'user_ids.*' => ['required', Rule::exists('users')->where(static function (Builder $query): void {
            $query->where('is_verified', true);
        })],
    ]
);

在上面的示例中,我们检查传入的每个 ID user_ids 数组存在于 users 表上的 id 列和用户的 is_verified 列设置为 true 。因此,如果我们传递未经验证的用户 ID,验证就会失败。

验证字段在数据库中是唯一的

类似于 exists 规则,Laravel 还提供了一个 unique 您可以使用该规则来检查数据库中的值是否唯一。

例如,假设您有一个 users 表并且您想确保 email 领域是独一无二的。您可以使用 unique 像这样规则:

use Illuminate\Support\Facades\Validator;

Validator::make(
    data: [
        'email' => 'mail@ashallendesign.co.uk',
    ],
    rules: [
        'email' => ['required', 'email', 'unique:users,email'],
    ]

在上面的例子中,我们检查 email 字段已设置,是一封电子邮件,并且在 users 表上的 email 柱子。

但是,如果我们尝试在用户可以更新其电子邮件地址的个人资料页面上使用此验证,会发生什么情况?验证将失败,因为上存在一行 users 包含用户尝试更新到的电子邮件地址的表。在这种情况下,我们可以使用 ignore 检查唯一性时忽略用户 ID 的方法:

use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make(
    data: [
        'email' => 'mail@ashallendesign.co.uk',
    ],
    rules: [
        'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)],
    ]

如果您选择使用 ignore 方法,您应该确保阅读 Laravel 文档中的此警告:

“你永远不应该将任何用户控制的请求输入传递​​到 ignore 方法。相反,您应该只传递系统生成的唯一 ID,例如来自 Eloquent 模型实例的自动递增 ID 或 UUID。否则,您的应用程序将容易受到 SQL 注入攻击。”

有时您可能还想添加额外的内容 where 的条款 unique 规则。您可能希望执行此操作以确保电子邮件地址对于特定团队是唯一的(这意味着不同团队中的其他用户可以拥有相同的电子邮件)。您可以通过将闭包传递给 where 方法:

use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

Validator::make(
    data: [
        'email' => 'mail@ashallendesign.co.uk',
    ],
    rules: [
        'email' => [
            'required',
            'email',
            Rule::unique('users')
                ->ignore($user->id)
                ->where(fn (Builder $query) => $query->where('team_id', $teamId));
        )],
    ],
);

创建您自己的验证规则

尽管 Laravel 附带了大量内置验证规则,但有时您可能需要创建自定义验证规则以适应特定用例。

值得庆幸的是,这在 Laravel 中也非常容易做到!

让我们看看如何构建自定义验证规则、如何使用它,以及如何为其编写测试。

出于本文的目的,我们不太关心我们正在验证的内容。我们只想了解创建自定义验证规则的一般结构以及如何测试它。因此,我们将创建一个简单的规则来检查字符串是否为回文。

以防万一您不知道,回文是一个单词、短语、数字或其他向前和向后读起来相同的字符序列。例如,“racecar”是一个回文,因为如果颠倒字符串,它仍然是“racecar”。然而,“laravel”不是回文,因为如果你反转字符串,它将是“levaral”。

首先,我们首先在项目路径中运行以下命令来创建一个新的验证规则:

php artisan make:rule Palindrome

这应该创建一个新的 App/Rules/Palindrome.php 为我们归档:

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class Palindrome implements ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param  \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString  $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        //
    }
}

Laravel 会自动调用 validate 规则运行时的方法。该方法采用三个参数:

  • $attribute :正在验证的属性的名称。
  • $value :正在验证的属性的值。
  • $fail :验证失败时可以调用的闭包。

所以我们可以在里面添加我们的验证逻辑 validate 像这样的方法:

declare(strict_types=1);

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;

final readonly class Palindrome implements ValidationRule
{
    /**
     * Run the validation rule.
     *
     * @param Closure(string): PotentiallyTranslatedString $fail
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if ($value !== strrev($value)) {
            $fail('The :attribute must be a palindrome.');
        }
    }
}

在上面的规则中,我们只是检查传递给规则的值是否与反转的值相同。如果不是,我们称 $fail 关闭并带有错误消息。这将导致该字段的验证失败。如果验证通过,则该规则将不执行任何操作,我们可以继续我们的应用程序。

现在我们已经创建了规则,我们可以在应用程序中使用它,如下所示:

use App\Rules\Palindrome;
use Illuminate\Support\Facades\Validator;

$validator = Validator::make(
    data: [
        'word' => 'racecar',
    ],
    rules: [
        'word' => [new Palindrome()],
    ]
);

尽管这是我们出于演示目的而创建的简单规则,但希望这能让您了解如何为您的应用程序构建更复杂的规则。

测试您自己的验证规则

就像应用程序中的任何其他代码一样,测试验证规则以确保它们按预期工作非常重要。否则,您可能会冒使用无法按预期工作的规则的风险。

为了了解如何做到这一点,让我们看一下如何测试我们在上一节中创建的回文规则。

对于这个特定的规则,我们要测试两种场景:

  • 当值为回文时,规则通过。
  • 当该值不是回文时,该规则失败。

在更复杂的规则中,您可能会遇到更多场景,但出于本文的目的,我们将保持简单。

我们将在我们的目录中创建一个新的测试文件 tests/Unit/Rules 名为的目录 PalindromeTest.php

让我们看一下测试文件,然后我们将讨论正在做什么:

declare(strict_types=1);

namespace Tests\Unit\Rules;

use App\Rules\PalindromeNew;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;

final class PalindromeTest extends TestCase
{
    #[Test]
    #[DataProvider('validValues')]
    public function rule_passes_with_a_valid_value(string $word): void
    {
        (new PalindromeNew())->validate(
            attribute: 'word',
            value: $word,
            fail: fn () => $this->fail('The rule should pass.'),
        );

        // We got to this point without any exceptions, so the rule passed.
        $this->assertTrue(true);
    }

    #[Test]
    #[DataProvider('invalidValues')]
    public function rule_fails_with_an_invalid_value(string $word): void
    {
        (new PalindromeNew())->validate(
            attribute: 'word',
            value: $word,
            fail: fn () => $this->assertTrue(true),
        );
    }

    public static function validValues(): array
    {
        return [
            ['racecar'],
            ['radar'],
            ['level'],
            ['kayak'],
        ];
    }

    public static function invalidValues(): array
    {
        return [
            ['laravel'],
            ['eric'],
            ['paul'],
            ['ash'],
        ];
    }
}

在上面的测试文件中,我们定义了两个测试: rule_passes_with_a_valid_valuerule_fails_with_an_invalid_value

正如测试名称所示,第一个测试确保当值是回文时规则通过,第二个测试确保当值不是回文时规则失败。

我们正在使用 PHPUnit\Framework\Attributes\DataProvider 属性,为测试提供要测试的有效和无效值的列表。这是保持测试干净并能够使用同一测试检查多个值的好方法。例如,如果有人要向 validValues 方法,测试将自动针对该值运行。

在里面 rule_passes_with_a_valid_value 测试,我们调用 validate 具有有效值的规则上的方法。我们已经关闭了 fail 参数(这是在规则内验证失败时调用的参数)。我们已经指定,如果执行了闭包(即验证失败),那么测试应该失败。如果我们在没有执行闭包的情况下完成测试,那么我们就知道规则通过了,并且可以添加一个简单的断言 assertTrue(true) 通过测试。

在里面 rule_fails_with_an_invalid_value 测试时,我们所做的与第一个测试相同,但这次我们向规则传递了一个无效值。我们已经指定,如果执行了闭包(即验证失败),那么测试应该通过,因为我们期望调用闭包。如果我们在没有执行闭包的情况下到达测试结束,则不会执行任何断言,并且 PHPUnit 应该为我们触发警告。但是,如果您希望更明确并确保测试失败而不是仅仅给出错误,那么您可能需要采用稍微不同的方法来编写测试。

结论

在本文中,我们了解了验证是什么以及它的重要性。我们将客户端验证与服务器端验证进行了比较,并探讨了为什么客户端验证永远不应该用作应用程序中的唯一验证形式。

我们还研究了一些我喜欢在 Laravel 应用程序中使用的方便的验证规则。最后,我们探讨了如何创建自己的验证规则并对其进行测试以确保其按预期工作。

希望您现在应该有足够的信心开始使用更多验证来提高应用程序的安全性和可靠性。


帖子 Laravel 验证终极指南 首先出现在 Laravel 新闻

加入 Laravel 时事通讯 直接在您的收件箱中获取所有此类最新的 Laravel 文章。