Laravel 11 自定义验证规则

Laravel 内置了丰富的验证规则,但实际业务中常常需要特定的验证逻辑,例如检查用户名是否包含敏感词、验证自定义的优惠券格式等。 本章将介绍如何创建和使用自定义验证规则,让你的验证逻辑更加灵活和可复用。

🎯 为什么需要自定义规则?

  • 满足业务特有的验证需求(如特定格式、业务规则)。
  • 复用复杂的验证逻辑,避免在控制器中重复编写。
  • 保持控制器代码简洁,将验证逻辑封装到规则类中。
  • 可以配合表单请求类,实现更优雅的验证。

📦 使用闭包定义规则

最简单的自定义规则方式是在验证时直接使用闭包。适用于逻辑简单且只在单个地方使用的情况。

use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    'username' => [
        'required',
        function ($attribute, $value, $fail) {
            if (str_contains($value, 'admin')) {
                $fail('用户名不能包含 "admin"。');
            }
        },
    ],
]);

if ($validator->fails()) {
    // 处理错误
}
>

闭包接收三个参数:$attribute(字段名)、$value(字段值)、$fail(回调函数)。当验证失败时调用 $fail 并传入错误消息。

💡 适用场景: 一次性的简单验证逻辑,不需要在其他地方复用。如果需要复用,推荐使用规则对象。

🏭 创建规则对象

对于需要复用的验证规则,最佳实践是创建独立的规则类。使用 Artisan 命令快速生成:

php artisan make:rule Uppercase

生成的规则类位于 app/Rules 目录,包含 passesmessage 两个方法:

namespace App\Rules;

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

class Uppercase implements ValidationRule
{
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (strtoupper($value) !== $value) {
            $fail('The :attribute must be uppercase.');
        }
    }
}

在验证器中使用规则对象:

use App\Rules\Uppercase;

$request->validate([
    'code' => ['required', new Uppercase],
]);
💡 提示: Laravel 11 推荐规则类实现 ValidationRule 接口(如上所示),也可以实现传统的 Rule 接口,但新方式更简洁。

🔧 带参数的规则对象

有时规则需要接收参数,如检查最小长度、特定条件等。可以通过构造函数传递参数:

namespace App\Rules;

use Illuminate\Contracts\Validation\ValidationRule;

class ContainsNumber implements ValidationRule
{
    protected $minCount;

    public function __construct($minCount = 1)
    {
        $this->minCount = $minCount;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (preg_match_all('/\d/', $value) < $this->minCount) {
            $fail("The :attribute must contain at least {$this->minCount} number(s).");
        }
    }
}

使用:

$request->validate([
    'password' => ['required', new ContainsNumber(2)],
]);

🌍 使用扩展验证器(Validator::extend)

如果你更喜欢使用字符串形式的规则(如 'uppercase'),可以通过 Validator::extend 注册规则。通常在服务提供者的 boot 方法中注册:

namespace App\Providers;

use Illuminate\Support\Facades\Validator;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Validator::extend('uppercase', function ($attribute, $value, $parameters, $validator) {
            return strtoupper($value) === $value;
        });

        // 自定义错误消息
        Validator::replacer('uppercase', function ($message, $attribute, $rule, $parameters) {
            return str_replace(':attribute', $attribute, 'The :attribute must be uppercase.');
        });
    }
}

现在可以在验证中直接使用 'uppercase' 规则:

$request->validate([
    'code' => 'required|uppercase',
]);
⚠️ 注意: 使用 extend 注册的规则是全局的,命名应避免与内置规则冲突。建议为自定义规则添加前缀,如 custom_uppercase

📝 自定义错误消息的国际化

对于规则对象,可以在 message 方法中返回错误消息。如果需要支持多语言,可以使用 __() 辅助函数:

public function message()
{
    return __('validation.custom.uppercase');
}

然后在语言文件(如 resources/lang/zh_CN/validation.php)中定义:

'custom' => [
    'uppercase' => '字段必须为大写字母。',
],

🎨 实战示例:敏感词过滤规则

假设我们需要一个规则,禁止用户名包含预定义的敏感词列表。

1. 创建规则类

namespace App\Rules;

use Illuminate\Contracts\Validation\ValidationRule;

class NoSensitiveWords implements ValidationRule
{
    protected $sensitiveWords = ['admin', 'root', 'test'];

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        foreach ($this->sensitiveWords as $word) {
            if (stripos($value, $word) !== false) {
                $fail("The :attribute cannot contain the word '{$word}'.");
                return;
            }
        }
    }
}

2. 在表单请求中使用

use App\Rules\NoSensitiveWords;

public function rules(): array
{
    return [
        'username' => ['required', 'string', 'min:3', 'max:20', new NoSensitiveWords],
        'email' => 'required|email|unique:users',
    ];
}

3. 在控制器中验证

public function store(StoreUserRequest $request)
{
    // 验证已自动通过
    User::create($request->validated());
    return redirect()->route('users.index');
}

⚡ 使用闭包规则快速验证(Laravel 11 新特性)

Laravel 11 允许你在验证规则数组中直接使用闭包,甚至支持 Rule::when() 条件规则,使规则定义更灵活。

use Illuminate\Validation\Rule;

$request->validate([
    'country' => 'required',
    'postal_code' => Rule::when(
        $request->country === 'US',
        ['required', 'regex:/^\d{5}$/'],
        ['nullable', 'string']
    ),
]);
>

✅ 最佳实践

  • 将可复用的验证逻辑封装为规则类,便于单元测试和复用。
  • 规则类的命名应清晰反映其功能,如 ContainsNumberValidPhoneNumber
  • 避免在规则类中执行复杂业务逻辑,应依赖服务类。
  • 对于简单的单次验证,使用闭包规则即可,不必过度设计。
  • 利用 Validator::extend 注册全局规则时,注意命名空间,避免与 Laravel 未来版本冲突。

❓ 常见问题

如果规则逻辑简单且只在一个地方使用,闭包规则更方便。如果需要复用或逻辑较复杂,推荐使用规则对象,便于测试和维护。

规则类本身只处理单个值,若要对数组元素验证,应使用星号语法,如 'items.*.code' => [new Uppercase],规则对象会逐个应用到每个元素。

在规则对象的 validate 方法中,可以通过 $this->validator->getData() 获取所有输入数据(需要先将 validator 注入)。更简单的方式是使用闭包规则,闭包可以访问 $request 或通过 use 传入外部变量。

📝 小结

自定义验证规则让 Laravel 的验证系统更具扩展性,能够适应各种复杂业务场景。无论是简单的闭包规则,还是可复用的规则对象,都能帮助你保持代码整洁和可维护性。 掌握这些技巧后,你可以轻松构建健壮的验证层,提升应用的数据质量和安全性。 下一章我们将学习会话管理,掌握用户状态维护的高级技巧。