什么是自定义验证规则
Laravel 已内置丰富的验证规则,但在实际开发中经常需要应用特有的校验逻辑。使用自定义验证规则可以将可复用的校验逻辑定义为类或闭包,并像标准规则一样使用。
定义自定义规则主要有两种方式。
规则对象 — 可复用性高、易于测试
闭包 — 适合仅使用一次的简单规则
规则对象
生成规则类
使用 make:rule Artisan 命令生成新的规则类。生成的类会放在 app/Rules 目录下。
php artisan make:rule Uppercase
实现 ValidationRule 接口
在生成的类中实现 validate 方法。校验失败时通过调用 $fail 闭包报告错误。
<? php
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.' );
}
}
}
传给 $fail 闭包的字符串中可以使用 :attribute 占位符,Laravel 会将其替换为字段名。
应用规则对象
将规则对象的实例传入校验数组即可。
use App\Rules\ Uppercase ;
$request -> validate ([
'name' => [ 'required' , 'string' , new Uppercase ],
]);
在 Form Request 的 rules() 方法中也可以同样使用。
public function rules () : array
{
return [
'name' => [ 'required' , 'string' , new Uppercase ],
];
}
使用翻译键的错误消息
除了硬编码错误消息,也可以使用翻译键。
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
if ( strtoupper ( $value ) !== $value ) {
$fail ( 'validation.uppercase' ) -> translate ();
}
}
在翻译文件 lang/zh_CN/validation.php 中添加消息:
return [
'uppercase' => ':attribute 必须为大写。' ,
// ...
];
添加多条错误消息
要为同一字段报告多条错误,可以多次调用 $fail。
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
if ( ! is_string ( $value )) {
$fail ( 'The :attribute must be a string.' );
return ;
}
if ( strlen ( $value ) < 8 ) {
$fail ( 'The :attribute must be at least 8 characters.' );
}
if ( ! preg_match ( '/[A-Z]/' , $value )) {
$fail ( 'The :attribute must contain at least one uppercase letter.' );
}
}
基于闭包的规则
对于在应用中仅使用一次的简单规则,可以不创建类,直接用闭包定义。
use Illuminate\Support\Facades\ Validator ;
use Closure ;
$validator = Validator :: make ( $request -> all (), [
'title' => [
'required' ,
'max:255' ,
function ( string $attribute , mixed $value , Closure $fail ) {
if ( $value === 'foo' ) {
$fail ( "The { $attribute } is invalid." );
}
},
],
]);
闭包规则可以内联定义,非常轻量,但难以复用或独立测试。因此,多个地方共用的逻辑推荐拆分为规则对象。
Implicit 规则(值为空时也执行)
默认情况下,当字段为空或不存在时,自定义规则不会被执行。若希望即使值为空也执行规则,生成类时可加上 --implicit 选项。
php artisan make:rule Uppercase --implicit
生成的类会实现 ImplicitRule 接口。该接口本身没有额外方法,仅作为向 Laravel 传递的信号。
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ ImplicitRule ;
use Illuminate\Contracts\Validation\ ValidationRule ;
class RequiredIfJapanese implements ValidationRule , ImplicitRule
{
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
if ( empty ( $value )) {
$fail ( 'The :attribute is required.' );
return ;
}
if ( ! preg_match ( '/ ^ [\p{Hiragana}\p{Katakana}\p{Han}ー] +$ /u' , $value )) {
$fail ( 'The :attribute must contain only Japanese characters.' );
}
}
}
ImplicitRule 只是告诉 Laravel「该属性是必需的」。当值为空时是否真的判定为验证失败,取决于 validate 方法的实现。
数据访问
DataAwareRule — 访问整个表单的数据
如果需要基于其他字段的值来校验,可实现 DataAwareRule 接口。校验开始前 setData 方法会被自动调用。
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ DataAwareRule ;
use Illuminate\Contracts\Validation\ ValidationRule ;
use Illuminate\Support\Facades\ DB ;
class UniqueForTenant implements DataAwareRule , ValidationRule
{
/**
* 校验对象的全部数据
*
* @var array < string , mixed>
*/
protected array $data = [];
public function __construct (
protected string $table ,
protected string $column = 'value' ,
) {}
/**
* 设置校验数据
*
* @param array < string , mixed> $data
*/
public function setData ( array $data ) : static
{
$this -> data = $data ;
return $this ;
}
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
$tenantId = $this -> data [ 'tenant_id' ] ?? null ;
$exists = DB :: table ( $this -> table )
-> where ( 'tenant_id' , $tenantId )
-> where ( $this -> column , $value )
-> exists ();
if ( $exists ) {
$fail ( 'The :attribute has already been taken for this tenant.' );
}
}
}
使用示例:
$request -> validate ([
'tenant_id' => 'required|integer' ,
'email' => [ 'required' , 'email' , new UniqueForTenant ( 'users' , 'email' )],
]);
ValidatorAwareRule — 访问 Validator 实例
若要访问 Validator 所拥有的全部信息(失败的规则、自定义消息等),可实现 ValidatorAwareRule 接口。
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ ValidationRule ;
use Illuminate\Contracts\Validation\ ValidatorAwareRule ;
use Illuminate\Validation\ Validator ;
class ConditionalFormat implements ValidationRule , ValidatorAwareRule
{
protected Validator $validator ;
public function setValidator ( Validator $validator ) : static
{
$this -> validator = $validator ;
return $this ;
}
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
// 若其他字段已校验失败,则跳过
if ( $this -> validator -> errors () -> has ( 'type' )) {
return ;
}
$type = $this -> validator -> getData ()[ 'type' ] ?? null ;
if ( $type === 'phone' && ! preg_match ( '/ ^ \+ ?[0-9\-\s] +$ /' , $value )) {
$fail ( 'The :attribute must be a valid phone number.' );
}
if ( $type === 'email' && ! filter_var ( $value , FILTER_VALIDATE_EMAIL )) {
$fail ( 'The :attribute must be a valid email address.' );
}
}
}
实战用例
中文字符检查
对全角/半角字符类型进行校验的规则。
php artisan make:rule ChineseOnly
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ ValidationRule ;
class ChineseOnly implements ValidationRule
{
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
// 仅允许汉字及常见标点
if ( ! preg_match ( '/ ^ [\p{Han}\s] +$ /u' , $value )) {
$fail ( ':attribute 必须只包含中文字符。' );
}
}
}
$request -> validate ([
'name_cn' => [ 'required' , 'string' , new ChineseOnly ],
]);
电话号码格式校验
校验中国大陆电话号码格式的规则。
php artisan make:rule ChinesePhone
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ ValidationRule ;
class ChinesePhone implements ValidationRule
{
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
// 支持带/不带分隔符,同时允许国际形式
$normalized = preg_replace ( '/[\s\-\(\)]/' , '' , $value );
$patterns = [
'/ ^ 1[3-9] \d {9} $ /' , // 中国大陆手机号
'/ ^ \+ 861[3-9] \d {9} $ /' , // 国际形式
];
foreach ( $patterns as $pattern ) {
if ( preg_match ( $pattern , $normalized )) {
return ;
}
}
$fail ( ':attribute 必须为有效的电话号码格式。' );
}
}
带租户 ID 的唯一性约束
多租户应用中常见的租户内唯一性约束。
创建规则类
php artisan make:rule TenantUnique
<? php
namespace App\Rules ;
use Closure ;
use Illuminate\Contracts\Validation\ DataAwareRule ;
use Illuminate\Contracts\Validation\ ValidationRule ;
use Illuminate\Support\Facades\ DB ;
class TenantUnique implements DataAwareRule , ValidationRule
{
protected array $data = [];
public function __construct (
protected string $table ,
protected string $column ,
protected ? int $ignoreId = null ,
) {}
public function setData ( array $data ) : static
{
$this -> data = $data ;
return $this ;
}
public function validate ( string $attribute , mixed $value , Closure $fail ) : void
{
$tenantId = $this -> data [ 'tenant_id' ] ?? null ;
$query = DB :: table ( $this -> table )
-> where ( 'tenant_id' , $tenantId )
-> where ( $this -> column , $value );
if ( $this -> ignoreId !== null ) {
$query -> where ( 'id' , '!=' , $this -> ignoreId );
}
if ( $query -> exists ()) {
$fail ( ':attribute 已被占用。' );
}
}
}
在 Form Request 中使用
<? php
namespace App\Http\Requests ;
use App\Rules\ TenantUnique ;
use Illuminate\Foundation\Http\ FormRequest ;
class CreateProjectRequest extends FormRequest
{
public function rules () : array
{
return [
'tenant_id' => 'required|integer|exists:tenants,id' ,
'name' => [
'required' ,
'string' ,
'max:100' ,
new TenantUnique ( 'projects' , 'name' ),
],
];
}
public function authorize () : bool
{
return true ;
}
}
更新时排除自身 ID
更新已有记录时,需将自身 ID 排除后进行重复检查。 class UpdateProjectRequest extends FormRequest
{
public function rules () : array
{
$projectId = $this -> route ( 'project' );
return [
'tenant_id' => 'required|integer|exists:tenants,id' ,
'name' => [
'required' ,
'string' ,
'max:100' ,
new TenantUnique ( 'projects' , 'name' , ignoreId : $projectId ),
],
];
}
}
在 ServiceProvider 中注册规则
使用 Validator::extend() 追加规则
使用 Validator::extend() 可以让自定义规则以字符串形式('rule_name')使用。在 AppServiceProvider 的 boot() 方法中注册。
<? php
namespace App\Providers ;
use Illuminate\Support\Facades\ Validator ;
use Illuminate\Support\ ServiceProvider ;
use Illuminate\Validation\ Validator as ValidatorInstance ;
class AppServiceProvider extends ServiceProvider
{
public function boot () : void
{
Validator :: extend ( 'chinese_only' , function ( string $attribute , mixed $value , array $parameters , ValidatorInstance $validator ) : bool {
return preg_match ( '/ ^ [\p{Han}\s] +$ /u' , $value ) === 1 ;
});
Validator :: replacer ( 'chinese_only' , function ( string $message , string $attribute ) : string {
return str_replace ( ':attribute' , $attribute , ':attribute 必须以中文输入。' );
});
}
}
通过 Validator::extend() 注册的规则可以用字符串指定。
$request -> validate ([
'name_cn' => 'required|chinese_only' ,
]);
Validator::extend() 是比规则对象更早的注册方式。新项目建议使用实现了 ValidationRule 接口的规则对象。
作为静态方法追加到 Rule 类
通过给 Rule Facade 添加宏,可以提供 Rule::myRule() 这样的流畅 API(Fluent API)。
use Illuminate\Validation\ Rule ;
Rule :: macro ( 'tenantUnique' , function ( string $table , string $column , ? int $ignoreId = null ) {
return new \App\Rules\ TenantUnique ( $table , $column , $ignoreId );
});
// 使用示例
$request -> validate ([
'name' => [ 'required' , Rule :: tenantUnique ( 'projects' , 'name' )],
]);
内部实现细节
来看看 Illuminate\Validation\Validator 是如何调用自定义规则的。
Validator 内部由 validateAttribute() 处理每个字段。若规则实现了 ValidationRule 接口,则会调用 validateUsingCustomRule() 方法。
// Illuminate\Validation\Validator::validateUsingCustomRule() 的简化版
protected function validateUsingCustomRule ( $attribute , $value , $rule )
{
// 若是 DataAwareRule,则注入全部数据
if ( $rule instanceof DataAwareRule ) {
$rule -> setData ( $this -> getData ());
}
// 若是 ValidatorAwareRule,则注入 Validator 自身
if ( $rule instanceof ValidatorAwareRule ) {
$rule -> setValidator ( $this );
}
// 调用 validate()
$rule -> validate ( $attribute , $value , function ( $message , $translate = false ) use ( $attribute , $rule ) {
// $fail 闭包 — 添加错误消息
$this -> errors () -> add ( $attribute , $this -> makeReplacements (
$message ,
$attribute ,
get_class ( $rule ),
[], // 追加的占位符替换(例如 ['min' => '8'])
));
});
}
ImplicitRule 的处理通过 isImplicit() 方法判断,即便值为空也会执行规则检查。
// 判断是否对空值执行规则
protected function isImplicit ( $rule ) : bool
{
return $rule instanceof ImplicitRule
|| in_array ( $rule , $this -> implicitRules );
}
可以同时实现 DataAwareRule 与 ValidatorAwareRule。同时实现两个接口的类会同时注入这两者。
相关页面
验证(入门) 了解在 Controller 与 Form Request 中的标准验证方法。