Laravel 验证的终极指南
发布于 作者: Ashley Allen
验证是任何 Web 应用程序的重要组成部分。它可以帮助防止安全漏洞、数据损坏以及在处理用户输入时可能出现的一系列其他问题。
在本文中,我们将探讨验证是什么以及为什么它很重要。然后我们将比较客户端验证和服务器端验证,并探讨为什么你不应该只依赖于应用程序中的客户端验证。
然后我们将介绍一些我在 Laravel 应用程序中喜欢使用的实用验证规则。最后,我们将介绍如何创建自己的验证规则并对其进行测试以确保其按预期工作。
什么是验证?
验证是在尝试使用数据之前检查数据是否有效的过程。这可以是检查简单的事情,例如请求中是否存在必填字段,到更复杂的检查,例如字段是否与特定模式匹配或在数据库中是否唯一。
通常,在 Web 应用程序中验证数据时,如果数据无效,你将希望向用户返回错误消息。
这有助于防止安全漏洞、数据损坏并提高数据准确性。因此,只有在数据有效的情况下,我们才能继续处理请求。
记住,来自用户的任何数据都不可信(至少在验证之前是不可信的)!
为什么验证很重要?
验证很重要,原因有很多,包括
提高安全性
在应用程序中验证数据的最重要的原因之一是提高安全性。通过在使用数据之前验证数据,你可以减少恶意输入被用来攻击你的应用程序或用户的可能性。
防止存储不正确的数据
想象一下,我们希望一个字段是整数,但用户却传递了一个文件。当我们尝试在应用程序的其他地方使用该数据时,这会导致各种问题。
另一个例子,想象一下你正在构建一个 Web 应用程序,允许用户对投票进行投票。投票只能在 `opens_at` 时间和 `App\Models\Poll` 模型上指定的 `closes_at` 时间之间进行。如果设置投票的人不小心将 `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
方法还接受两个可选参数:messages
和 attributes
。这些可以用来定制返回给用户的错误消息,但本文不会介绍它们。
让我们看一个如何验证两个字段的示例。
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'], ]);
在上面的示例中,我们可以看到我们正在验证两个字段:title
和 body
。我们对两个字段的值进行了硬编码以使示例更清晰,但在实际项目中,通常会从请求中获取这些字段。我们正在检查 title
字段是否已设置,是否为字符串,以及其最大长度是否为 100 个字符。我们还检查 description
字段是否已设置,是否为字符串,以及其最大长度是否为 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
,则会向用户返回403 Forbidden
响应。如果该方法返回true
,则会运行验证规则。 -
rules
:此方法用于定义应在请求上运行的验证规则。该方法应返回一个包含应在请求上运行的规则的数组。
在 rules
方法中,我们指定 name
字段必须设置,必须为字符串,并且其最大长度不得超过 100 个字符。我们还指定 email
字段必须设置,必须为电子邮件,并且必须在 users
表(在 email
列上)中是唯一的。最后,我们指定 password
字段必须设置,并且必须通过我们设置的默认密码验证规则(我们将在后面介绍密码验证)。
如您所见,这是一种将验证逻辑与控制器逻辑分离的好方法,我发现它使代码更易于阅读和维护。
Laravel 中方便的验证规则
正如我已经提到的,Laravel 验证系统非常强大,使您能够轻松地将验证添加到您的应用程序中。
在本节中,我们将快速了解一些我认为对您大多数人来说在您的应用程序中非常有用且我认为您会喜欢的方便的验证规则。
如果您有兴趣查看 Laravel 中可用的所有规则,可以在 Laravel 文档中找到它们:https://laravel.net.cn/docs/11.x/validation
验证数组
您需要运行的一种常见的验证类型是验证数组。这可以是任何内容,从验证传递的 ID 数组是否全部有效,到验证请求中传递的对象数组是否都具有某些字段。
让我们看一个如何验证数组的示例,然后我们将讨论正在做什么。
use Illuminate\Support\Facades\Validator;use Illuminate\Validation\Rule; Validator::make( data: [ 'users' => [ [ 'name' => 'Eric Barnes', ], [ 'name' => 'Paul Redmond', ], [ 'name' => 'Ash Allen', ], ], ], rules: [ 'users' => ['required', 'array'], 'users.*' => ['required', 'array:name,email'], 'users.*.name' => ['required', 'string', 'max:100'], 'users.*.email' => ['required', 'email', 'unique:users,email'], ]);
在上面的示例中,我们传递了一个对象数组,每个对象都具有一个 name
和 email
字段。
对于验证,我们首先定义 users
字段是否已设置以及是否为数组。然后,我们指定数组的每个项(使用 users.*
作为目标)都是一个包含 name
和 email
字段的数组。
然后,我们指定 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_at
和 closes_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
作为参数传递给 opens_at
字段的 after
规则。Laravel 将尝试使用 strtotime
函数将此字符串转换为有效的 DateTime
对象,并将其与之进行比较。
对于 closes_at
字段,我们已将 opens_at
作为参数传递给 after_or_equal
规则。Laravel 将自动检测到这是一个正在验证的另一个字段,并将这两个字段进行比较。
类似地,Laravel 还提供 before
和 before_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'], ]);
验证密码
作为 Web 开发人员,我们的工作是尽力帮助用户在线安全。我们可以做到这一点的一种方法是在我们的应用程序中推广良好的密码实践,例如要求密码具有一定长度、包含特定字符等。
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 允许我们定义一组默认的密码验证规则,我们可以在整个应用程序中使用。我们可以通过使用 Password::defaults()
方法在我们的 App\Providers\AppServiceProvider
的 boot
方法中定义一组默认规则来做到这一点。
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
方法来指定我们想要允许的文件类型。
min
和 max
方法也可以接受包含其他后缀的字符串,表示文件大小单位。例如,我们还可以使用。
-
10kb
-
10mb
-
10gb
-
10tb
此外,我们还可以使用 `Illuminate\Validation\Rules\File` 类中的 `image` 方法来确保文件是图像。
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)。如果您更喜欢这样做,您可能想查看我的 在 Laravel 中使用 FilePond 上传文件 文章,该文章将向您展示如何执行此操作,您可能想采取的不同验证方法以及如何对其进行测试。
验证数据库中是否存在字段
您可能希望进行的另一个常见检查是确保数据库中存在某个值。
例如,假设您的应用程序中有一些用户,并且您创建了一条路由,以便您可以批量将它们分配到一个团队。因此,在您的请求中,您可能希望验证请求中传递的 `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'], ]);
在上面的示例中,我们检查 `user_ids` 数组中传递的每个 ID 是否都存在于 `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); })], ]);
在上面的示例中,我们检查 `user_ids` 数组中传递的每个 ID 是否都存在于 `users` 表的 `id` 列中,并且用户的 `is_verified` 列设置为 `true`。因此,如果我们要传递未经验证的用户的 ID,则验证将失败。
验证数据库中字段是否唯一
与 `exists` 规则类似,Laravel 还提供了一个 `unique` 规则,您可以使用它来检查数据库中的值是否唯一。
例如,假设您有一个 `users` 表,您想确保 `email` 字段是唯一的。您可以像这样使用 `unique` 规则
use Illuminate\Support\Facades\Validator; Validator::make( data: [ ], 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: [ ], rules: [ 'email' => ['required', 'email', Rule::unique('users')->ignore($user->id)], ]
如果您确实选择使用 `ignore` 方法,您应该确保阅读 Laravel 文档中的此警告
"您永远不应该将任何用户控制的请求输入传递给 `ignore` 方法。相反,您应该只传递系统生成的唯一 ID,例如来自 Eloquent 模型实例的自增 ID 或 UUID。否则,您的应用程序将容易受到 SQL 注入攻击。"
有时您可能还想在 `unique` 规则中添加其他 `where` 子句。您可能希望这样做以确保电子邮件地址对于特定团队是唯一的(这意味着不同团队中的其他用户可以具有相同的电子邮件)。您可以通过将闭包传递给 `where` 方法来实现。
use Illuminate\Database\Query\Builder;use Illuminate\Support\Facades\Validator;use Illuminate\Validation\Rule; Validator::make( data: [ ], 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_value` 和 `rule_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 应用程序中喜欢使用的便捷验证规则。最后,我们探讨了如何创建自己的验证规则并对其进行测试以确保其按预期工作。
希望您现在应该有足够的信心开始使用更多验证来提高应用程序的安全性 and 可靠性。