somnambulist/validation

对rakit/validation的重写,提供类似于Laravel的验证功能作为独立的库

1.12.0 2024-08-01 21:16 UTC

This package is auto-updated.

Last update: 2024-09-01 21:34:44 UTC


README

GitHub Actions Build Status Issues License PHP Version Current Version

这是对rakit/validation的重写,一个类似于Laravel验证的独立验证器。与rakit/validation一样,这个库在使用时没有任何其他依赖。

请注意,内部API与rakit/validation有很大不同。

跳转到规则

要求

  • PHP 8.0+
  • ext/mb-string

安装

使用composer安装,或者从github.com检出/pull文件。

  • composer require somnambulist/validation

使用方法

使用这个库验证数据有两种方式:使用make创建验证对象,然后使用validate验证;或者直接使用validate

例如

使用make

<?php

require('vendor/autoload.php');

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->make($_POST + $_FILES, [
    'name'                  => 'required',
    'email'                 => 'required|email',
    'password'              => 'required|min:6',
    'confirm_password'      => 'required|same:password',
    'avatar'                => 'required|uploaded_file:0,500K,png,jpeg',
    'skills'                => 'array',
    'skills.*.id'           => 'required|numeric',
    'skills.*.percentage'   => 'required|numeric'
]);
$validation->validate();

if ($validation->fails()) {
    // handling errors
    $errors = $validation->errors();
    echo "<pre>";
    print_r($errors->firstOfAll());
    echo "</pre>";
    exit;
} else {
    // validation passes
    echo "Success!";
}

或通过validate

<?php

require('vendor/autoload.php');

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->validate($_POST + $_FILES, [
    'name'                  => 'required',
    'email'                 => 'required|email',
    'password'              => 'required|min:6',
    'confirm_password'      => 'required|same:password',
    'avatar'                => 'required|uploaded_file:0,500K,png,jpeg',
    'skills'                => 'array',
    'skills.*.id'           => 'required|numeric',
    'skills.*.percentage'   => 'required|numeric'
]);

if ($validation->fails()) {
	// handling errors
	$errors = $validation->errors();
	echo "<pre>";
	print_r($errors->firstOfAll());
	echo "</pre>";
	exit;
} else {
	// validation passes
	echo "Success!";
}

强烈建议使用依赖注入容器并将Factory作为单例存储,而不是创建新实例。这将减少创建验证实例的代价,并使自定义规则更容易管理。

属性别名

rakit/validation不同,属性名称不会被任何方式转换;相反,如果您想命名属性,则必须使用别名。

别名可以通过几种方式定义:在规则本身中,或通过在验证中添加别名。请注意,别名应该在调用validate之前设置。

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->make([
	'province_id' => $_POST['province_id'],
	'district_id' => $_POST['district_id']
], [
	'province_id:Province' => 'required|numeric',
	'district_id:District' => 'required|numeric'
]);

// or set the aliases:
$validation->setAlias('province_id', 'Province');
$validation->setAlias('district_id', 'District');

// then validate it
$validation->validate();

验证、有效和无效数据

验证后,数据结果保留在每个验证实例中。例如

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory())->validate([
    'title' => 'Lorem Ipsum',
    'body' => 'Lorem ipsum dolor sit amet ...',
    'published' => null,
    'something' => '-invalid-'
], [
    'title' => 'required',
    'body' => 'required',
    'published' => 'default:1|required|in:0,1',
    'something' => 'required|numeric'
]);

现在您可以获取验证后的数据,只有有效数据,或者只有无效数据

$validatedData = $validation->getValidatedData();
// [
//     'title' => 'Lorem Ipsum',
//     'body' => 'Lorem ipsum dolor sit amet ...',
//     'published' => '1' // notice this
//     'something' => '-invalid-'
// ]

$validData = $validation->getValidData();
// [
//     'title' => 'Lorem Ipsum',
//     'body' => 'Lorem ipsum dolor sit amet ...',
//     'published' => '1'
// ]

$invalidData = $validation->getInvalidData();
// [
//     'something' => '-invalid-'
// ]

可用规则

点击显示详情。

accepted

此规则下的字段必须是'on''yes''1''true'(字符串"true")或true之一。

after:tomorrow

此规则下的字段必须是一个在给定最小值之后的日期。

参数应该是任何可以被strtotime解析的有效字符串。例如

  • after:next week
  • after:2016-12-31
  • after:2016
  • after:2016-12-31 09:56:02
alpha

此规则下的字段必须是全部字母字符。

alpha_num

此规则下的字段必须是全部字母数字字符。

alpha_dash

此规则下的字段可以包含字母数字字符,以及破折号和下划线。

alpha_spaces

此规则下的字段可以包含字母字符,以及空格。

any_of:value,value,value

in的一种变体:这里的值(默认用逗号分隔)必须都在给定的值中。例如:order => 'name,date'与规则any_of:name,id将失败验证,因为date不是允许的值之一。分隔符可以通过在规则实例上调用separator()来更改。

use Somnambulist\Components\Validation\Factory;
use Somnambulist\Components\Validation\Rules\AnyOf;

$validation = $factory->validate([
    'field' => 'foo;bar;example'
], [
    'field' => $factory->rule('any_of')->separator(';')->values(['foo', 'bar']),
]);

$validation->passes(); // true if field only contains the values in any_of

in一样,可以通过在规则上调用->strict(true)来执行严格的匹配。

此规则对于允许以逗号分隔数据作为单个参数的API很有用,例如JsonAPI include、order等。如果源已经是数组,则可以使用array|in:...代替。

array

此规则下的字段必须是一个数组。

array_must_have_keys:value,value,value

数组必须包含所有指定的键才能有效。这有助于确保嵌套数组符合规定的格式。通过使用每个键的单独规则与required也可以达到相同的效果。注意,这仍然允许存在额外的键,它只是验证特定键的存在。

此规则最好与array规则一起使用,尽管它也可以单独使用。

use Somnambulist\Components\Validation\Factory;

$validation = $factory->validate([
    'filters' => ['foo' => 'bar', 'baz' => 'example']
], [
    'filters' => 'array|array_must_have_keys:foo,bar,baz',
]);

$validation->passes(); // true if filters has all the keys in array_must_have_keys

以下示例功能等效

use Somnambulist\Components\Validation\Factory;

$validation = $factory->validate([
    'filters' => ['foo' => 'bar', 'baz' => 'example']
], [
    'filters' => 'array|array_must_have_keys:foo,bar,baz',
    'filters.foo' => 'string|between:1,50',
    'filters.bar' => 'numeric|min:1',
    'filters.baz' => 'uuid',
]);

$validation = $factory->validate([
    'filters' => ['foo' => 'bar', 'baz' => 'example']
], [
    'filters' => 'array',
    'filters.foo' => 'required|string|between:1,50',
    'filters.bar' => 'required|numeric|min:1',
    'filters.baz' => 'required|uuid',
]);
array_can_only_have_keys:value,value,value

数组只能包含指定的键,任何不存在的键都会使验证失败。默认情况下,关联数据对键与值没有限制。例如:您有一个搜索框的过滤器传递给SQL,只有指定的键应该允许发送,而不仅仅是过滤器的数组中的任何值。

此规则最好与array规则一起使用,尽管它也可以单独使用。

use Somnambulist\Components\Validation\Factory;

$validation = $factory->validate([
    'filters' => ['foo' => 'bar', 'baz' => 'example']
], [
    'filters' => 'array|array_can_only_have_keys:foo,bar',
]);

$validation->passes(); // true if filters only has the keys in array_can_only_have_keys
before:yesterday

此规则下的字段必须是在给定最大值之前的日期。

这和after规则的工作方式相同。传递任何可以被strtotime解析的东西。

between:min,max

此规则下的字段的大小必须在min和max参数之间。值大小以与minmax规则相同的方式计算。

您还可以使用此规则验证上传文件的大小。

$validation = $validator->validate([
    'photo' => $_FILES['photo']
], [
    'photo' => 'required|between:1M,2M'
]);
布尔型

此规则下的字段必须是布尔型。接受的输入有truefalse10"1""0"

回调

定义一个自定义回调来验证值。此规则不能使用字符串语法注册。要使用此规则,您必须使用数组语法,并明确指定callback,或者传递闭包。

$validation = $validator->validate($_POST, [
    'even_number' => [
        'required',
        function ($value) {
            // false = invalid
            return (is_numeric($value) AND $value % 2 === 0);
        },
        'callback' => fn ($v) => is_numeric($v) && $v % 2 === 0,
    ]
]);

您可以通过返回一个字符串来设置自定义消息,而不是返回false。为了允许消息翻译,而不是返回一个字面字符串,返回一个消息密钥,并将其添加到Factory的消息包中。

注意:返回消息字符串将在未来的版本中删除,需要只返回布尔响应。相反,直接在返回true/false之前设置消息字符串,通过$this->message = "";

$validation = $validator->validate($_POST, [
    'even_number' => [
        'required',
        function ($value) {
            if (!is_numeric($value)) {
                return ":attribute must be numeric.";
            }
            if ($value % 2 !== 0) {
                return ":attribute is not even number.";
            }
            
            return true; // always return true if validation passes
        }
    ]
]);

注意:回调闭包绑定到规则实例,允许通过$this访问规则属性。

date:format

此规则下的字段必须是符合给定格式的有效日期。参数format是可选的,默认格式是Y-m-d

默认值/默认值

如果属性没有值,则此默认值将在验证数据中替换使用。

例如,如果您有如下验证

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->validate([
    'enabled' => null
], [
    'enabled' => 'default:1|required|in:0,1'
    'published' => 'default:0|required|in:0,1'
]);

$validation->passes(); // true

// Get the valid/default data
$valid_data = $validation->getValidData();

$enabled = $valid_data['enabled'];
$published = $valid_data['published'];

验证通过,因为enabledpublished的默认值设置为10,这是有效的。

different:another_field

same相反;此规则下的字段值必须与another_field值不同。

digits:value

验证字段必须是数字,并且必须具有与value精确相同的长度。

digits_between:min,max

验证字段必须是数字,并且其长度必须在给定的minmax之间。

电子邮件

此验证字段必须是一个有效的电子邮件地址,根据内置的PHP过滤器扩展。

有关详细信息,请参阅FILTER_VALIDATE_EMAIL

ends_with:another_field

此验证字段必须以another_field结束。比较可以是字符串、数字或数组元素。

exists:table,column (数据库)

此验证字段必须存在于给定的表中。这并不检查唯一性,只确保表中至少有一条记录包含所提供的值和列。

要使用此规则,您必须提供一个DBAL连接。这应该通过依赖注入来完成。

例如

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->validate([
    'country' => 'GBR'
], [
    'country' => 'exists:countries,id',
]);

$validation->passes(); // true if table countries has a record with id GBR

为了进行更精细的验证,可以通过调用->where()来设置闭包,以修改底层查询。闭包将传递一个Doctrine\DBAL\Query\QueryBuilder实例。

use Doctrine\DBAL\Query\QueryBuilder;
use Somnambulist\Components\Validation\Factory;
use Somnambulist\Components\Validation\Rules\Exists;

$factory    = new Factory;
$factory->addRule('exists', new Exists($dbalConn));

$validation = $factory->validate([
    'country' => 'GBR'
], [
    'country' => $factory->rule('exists')->table('countries')->column('id')->where(fn (QueryBuilder $qb) => $qb->andWhere('active = 1')),
]);

$validation->passes(); // true if table countries has a record with id GBR and it is active
扩展:extension_a,extension_b,...

此规则下的字段必须以其中之一列出的扩展名结尾。

这对于验证给定路径或URL的文件类型非常有用。应使用mimes规则来验证上传文件。

如果您需要严格的MIME检查,应实现一个自定义的MimeTypeGuesser,它可以利用服务器端文件检查器,该检查器使用MIME库。

浮点数

此规则下的字段必须是浮点数,例如:0.0 12.3456等。值可以是一个包含浮点数的字符串。请注意,整数和0(零)将使用此规则进行验证失败。

:value_1,value_2,...

此规则下的字段必须包含在给定的值列表中。

为了帮助构建字符串规则,In(和NotIn)规则有一个辅助方法

use Somnambulist\Components\Validation\Factory;
use Somnambulist\Components\Validation\Rules\In;

$factory = new Factory();
$validation = $factory->validate($data, [
    'enabled' => [
        'required',
        In::make([true, 1])
    ]
]);

此规则使用in_array进行验证,默认情况下不执行严格检查。如果您需要严格检查,可以像这样调用规则

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$validation = $factory->validate($data, [
    'enabled' => [
        'required',
        $factory->rule('in')->values([true, 1])->strict()
    ]
]);

然后'enabled'值应该是布尔值true或整数1

整数

正在验证的字段必须是整数。

IP

此规则下的字段必须是有效的IPv4或IPv6地址。

IPv4

此规则下的字段必须是有效的IPv4地址。

IPv6

此规则下的字段必须是有效的IPv6地址。

JSON

此验证字段必须是有效的JSON字符串。

长度:number

此验证字段必须是长度恰好指定的字符串。

小写

此验证字段必须为小写。

最大值:number

此规则下的字段的大小必须小于或等于给定的数字。值大小以与min规则相同的方式计算。

您还可以使用此规则验证上传文件的最大大小。

$validation = $validator->validate([
    'photo' => $_FILES['photo']
], [
    'photo' => 'required|max:2M'
]);
mimes:extension_a,extension_b,...

正在验证的$_FILES项必须有一个与列出的扩展名对应的MIME类型。

此方法基于文件扩展名,而不是客户端发送的标头或嵌入式文件类型。如果您需要严格的MIME类型验证,建议实现一个自定义的MimeTypeGuesser,它使用完整的MIME类型查找库,并替换内置的MIME规则。

可以通过依赖注入向现有的猜测器添加额外的MIME类型,并保持MIME类型猜测器作为服务。

最小值:number

此规则下的字段的大小必须大于或等于给定的数字。

对于字符串值,大小对应于字符数。对于整数或浮点值,大小对应于其数值。对于数组,大小对应于数组的计数。如果您的值是数字字符串,您可以使用numeric规则将其大小视为数值,而不是字符数。

您还可以使用此规则验证上传文件的最小大小。

$validation = $validator->validate([
    'photo' => $_FILES['photo']
], [
    'photo' => 'required|min:1M'
]);
不在其中:value_1,value_2,...

此规则下的字段不得包含在给定的值列表中。

此规则也使用 in_array,并且可以通过与 In 相同的方式启用严格检查。

可空

此规则下的字段可能为空。

数值

此规则下的字段必须是数值。

存在

此规则下的字段必须属于输入集,无论值是什么。

禁止

此规则下的字段不允许。

禁止_if

如果 another_field 提供了任何值,则此规则下的字段不允许。

禁止_unless

除非 another_field 有这些值之一,否则此规则下的字段不允许。这是 禁止_if 的逆操作。

正则表达式:/your-regex/

此规则下的字段必须匹配给定的正则表达式。注意:如果您需要使用 |,则正则表达式规则必须以数组格式编写,而不是字符串。例如

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory())->validate([
    'field' => 'value'
], [
    'field' => [
        'required',
        'regex' => '/(this|that|value)/'
    ]
])
拒绝

此规则下的字段必须有一个值与拒绝相对应,即 0(零)、"0"、false、no、"false"、off。这是 接受 规则的逆操作。

必填

此验证下的字段必须存在并且不为 '空'。

以下是一些示例

对于上传的文件,$_FILES['key']['error'] 必须不是 UPLOAD_ERR_NO_FILE

必填_if:another_field,value_1,value_2,...

如果 another_field 字段等于任何值,则此规则下的字段必须存在并且不为空。

例如 必填_if:something,1,yes,on 将在 something 的值是 1'1''yes''on' 之一时生效。

必填_unless:another_field,value_1,value_2,...

除非 another_field 字段等于任何值,否则此验证下的字段必须存在并且不为空。

requires:field_1,field_2,...

此验证下的字段要求指定的字段在输入数据中存在并且不为空。

例如:字段 b "requires:a";如果 a 不是一个存在的值,或者具有一个 "empty" 值,那么验证将失败。"empty" 是 false、空字符串或 null。

这是 required_with 的扩展,然而,当与 sometimesnullable 一起使用时,规则将失败。例如:如果 b "requires:a" 并且 "a" 被允许为 nullable,b 将失败,因为它明确要求具有值的 a。

required_with:field_1,field_2,...

仅当其他指定的字段之一存在时,此验证下的字段必须存在并且不为空。

注意:如果定义此规则的规则是 sometimesnullable,则此规则的行为可能会被规避。例如:如果 a 是 "required_with:b",但 a 也只是 sometimes 存在,那么 required_with 将永远不会触发,因为 sometimes 规则会否定它。a 还需要显式传递以触发规则。

required_without:field_1,field_2,...

仅当其他指定的字段之一不存在时,此验证下的字段必须存在并且不为空。

required_with_all:field_1,field_2,...

仅当所有其他指定的字段都存在时,此验证下的字段必须存在并且不为空。

required_without_all:field_1,field_2,...

仅当所有其他指定的字段都不存在时,此验证下的字段必须存在并且不为空。

same:another_field

此规则下的字段值必须与 another_field 具有相同的值。

sometimes

如果字段存在于输入数据中,则应该对字段进行验证。例如:field => sometimes|required|email

starts_with:another_field

此验证下的字段必须以 another_field 开头。比较可以是字符串、数字和数组元素。

字符串

根据此规则,该字段必须是PHP字符串。

唯一:表,列,忽略,忽略列(数据库)

此验证下的字段必须在给定的表中是唯一的。可选:可以忽略一个值,如果提供了ignore_column,这可以是一个替代列的值。

要使用此规则,您必须提供一个DBAL连接。这应该通过依赖注入来完成。

例如

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->validate([
    'email' => 'foo@example.org'
], [
    'email' => 'email|unique:users,email',
]);

$validation->passes(); // true if table users does not contain the email

忽略当前用户的电子邮件地址

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory)->validate([
    'email' => 'foo@example.org'
], [
    'email' => 'email|unique:users,email,10,id',
]);

$validation->passes(); // true if table users ignoring id 10, does not contain email

为了进行更精细的验证,可以通过调用->where()来设置闭包,以修改底层查询。闭包将传递一个Doctrine\DBAL\Query\QueryBuilder实例。

use Doctrine\DBAL\Query\QueryBuilder;
use Somnambulist\Components\Validation\Factory;
use Somnambulist\Components\Validation\Rules\Unique;

$factory    = new Factory;
$factory->addRule('unique', new Unique($dbalConn));

$validation = $factory->validate([
    'email' => 'foo@example.org'
], [
    'email' => $factory->rule('unique')->table('users')->column('email')->where(fn (QueryBuilder $qb) => $qb->andWhere('active = 1')),
]);

$validation->passes(); // true if table users does not contain an active email
上传文件:min_size,max_size,extension_a,extension_b,...

此规则将验证来自 $_FILES 的数据。此规则下的字段有以下条件

  • $_FILES['key']['error'] 必须是 UPLOAD_ERR_OKUPLOAD_ERR_NO_FILE。对于 UPLOAD_ERR_NO_FILE,您可以使用 required 规则进行验证。
  • 如果提供了最小大小,上传文件的大小 不能低于 最小大小。
  • 如果提供了最大大小,上传文件的大小 不能高于 最大大小。
  • 如果提供了文件类型,则MIME类型必须是给定类型之一。

当使用字符串定义时,必须同时提供大小限制。要指定只有最大大小,请使用工厂方法获取规则并使用方法链。

以下是一些示例定义和说明

  • uploaded_file:上传文件是可选的。当它不为空时,它必须是 ERR_UPLOAD_OK
  • required|uploaded_file:上传文件是必需的,并且必须是 ERR_UPLOAD_OK
  • uploaded_file:0,1M:上传文件大小必须在0 - 1 MB之间,但上传文件是可选的。
  • required|uploaded_file:0,1M,png,jpeg:上传文件大小必须在0 - 1MB之间,并且MIME类型必须是 image/jpegimage/png

对于多个文件上传,PHP使用格式 _FILES[key][name][0..n+1] (更多详情请参阅PHP手册)。如果使用点表示法提供属性键,则文件数组将自动重新排序为相关属性的嵌套数组。这允许使用相同的规则验证多个文件。这仅在属性名使用点表示法时才会发生。

从1.4.2版本开始,不使用点规则允许多个文件将引发 RuntimeException

例如,如果您有如下输入文件

<input type="file" name="photos[]"/>
<input type="file" name="photos[]"/>
<input type="file" name="photos[]"/>

您可以使用以下方式验证所有文件

$validation = (new Factory)->validate($_FILES, [
    'photos.*' => 'uploaded_file:0,2M,jpeg,png'
]);

// or

$validation = (new Factory)->validate($_FILES, [
    'photos.*' => 'uploaded_file|max:2M|mimes:jpeg,png'
]);

或者如果您有如下输入文件

<input type="file" name="images[profile]"/>
<input type="file" name="images[cover]"/>

您可以如此验证它

$validation = (new Factory)->validate($_FILES, [
    'images.*' => 'uploaded_file|max:2M|mimes:jpeg,png',
]);

// or

$validation = (new Factory)->validate($_FILES, [
    'images.profile' => 'uploaded_file|max:2M|mimes:jpeg,png',
    'images.cover' => 'uploaded_file|max:5M|mimes:jpeg,png',
]);
大写

此验证下的字段必须是大写。

URL

根据此规则,该字段必须是有效的URL格式。默认情况下,验证常见的格式:any_scheme://...。如果您愿意,可以指定特定的URL方案。

例如

$validation = (new Factory)->validate($inputs, [
    'random_url' => 'url',          // value can be `any_scheme://...`
    'https_url' => 'url:http',      // value must be started with `https://`
    'http_url' => 'url:http,https', // value must be started with `http://` or `https://`
    'ftp_url' => 'url:ftp',         // value must be started with `ftp://`
    'custom_url' => 'url:custom',   // value must be started with `custom://`
]);

rakit、mailto和JDBC不同,不支持。请实现自定义规则或正则表达式来验证这些。

UUID

此验证下的字段必须是有效的UUID,而不是nil UUID字符串。

可选验证与空值验证

有时属性可以省略或可以是null。这些情况应谨慎处理,并且验证后的结果可能不同。

对于可选属性,可以在验证数据中省略,即仅在数据存在时进行验证,可以使用 sometimes 规则。如果指定了此规则,则该属性可以完全省略,或者必须满足验证标准。这对于像搜索过滤器或不是始终必需的分页标记等非常有用。

[
    'filters' => 'sometimes|array',
]

在此示例中 filters 完全可选,但如果指定了,则应该是值的数组。传递 [filters => ''] 将不是有效的,它必须是: [filters => []]

有时,属性不是可选的,而是应该是未定义的,即 null。通常,使用 sometimes 并省略值更可取,但可能有必要保持具有 null 值的属性。在这些情况下,使用 nullable 规则。这将允许属性存在而不带任何值。例如:用户的生日可能是可空的或日期: nullable|date

rakit/validation不同,使用可空数据可能会引起问题,因为这个库在整个过程中都使用严格的类型。这意味着许多测试字符串、数组或数字错误的规则,因为它们接收到了null。这是规则定义过程中的一个歧义。例如,规则name: string|max:200隐式地意味着name应该是一个字符串,最多200个字符——《null》不应有效,但为了保持部分兼容性,它将允许null

这个库的下一个主要版本将移除这种处理,并使这种类型的定义必须字段存在且值不为空(除非显式允许为空)。要允许空值,需要显式定义nullable规则。因此,始终使用可空或有时是良好的实践。

验证数组数据

该库可以通过使用点符号来定义数组结构来验证复杂的数据数组。有一些变体和一些边缘情况需要注意,以防止问题。

最常见的情况是想要允许一个类似于本说明文档中早期示例的选项数组。

[
    'skills'              => 'array',
    'skills.*.id'         => 'required|numeric',
    'skills.*.percentage' => 'required|numeric'
],

早期的规则是设置为验证与用户相关的数据,包括技能数组。每个技能都有一个id和一个百分比值。在这种情况下,父键skills应该定义规则array,这是为了确保数据实际上是一个数组。然后使用*引用每个技能属性,以表示技能属性中有多个值。

这些规则将验证以下数组结构

[
    'skills' => [
        [
            'id' => 3,
            'percentage' => 50,
        ],
        [
            'id' => 17,
            'percentage' => 50,
        ],
    ]
]

较少见的情况是没有父键的数组数组。在这种情况下,没有前缀,每个子键以*开头。在这种情况下,你应该小心不要将标准键-值对与数组数据混合。

例如

[
    '*.id'         => 'required|numeric',
    '*.percentage' => 'required|numeric'
]

将用于验证以下数组结构

[
    [
        'id' => 3,
        'percentage' => 50,
    ],
    [
        'id' => 17,
        'percentage' => 50,
    ],
]

为了避免问题,你需要确保数据不会包括

[
    'name' => 'foo bar',
    [
        'id' => 3,
        'percentage' => 50,
    ],
    [
        'id' => 17,
        'percentage' => 50,
    ],
]

依赖验证规则和数组数据

某些规则用于确定某些键的存在或是否需要。通常这些使用标准键名,例如:confirm_password应与password字段相同,所以规则写为:same:password

然而,对于数组数据,这不会工作,因为属性不是属性名,而是该属性的路径

以相同的技能数组为例,假设我们想要在技能为新时要求标签。如果指定为required_if:id:null,那么验证将查找数据根中的属性名id,但它不存在,或者它可能找到错误的键。

相反,我们必须将规则显式绑定到相同的技能键,规则写为:required_if:skills.*.id,null。如果不这样做,则规则将被忽略或失败。同样,当使用数组数组时,在数组中引用其他字段时应该以*为前缀,例如:required_if:*.id,null

以下为两种语法的示例

[
    'skills.*.id'         => 'sometimes|numeric',
    'skills.*.percentage' => 'required|numeric',
    'skills.*.title'      => 'required_if:skills.*.id,null|string',
]

数组数组

[
    '*.id'         => 'sometimes|numeric',
    '*.percentage' => 'required|numeric',
    '*.title'      => 'required_if:*.id,null|string',
]

验证消息

验证消息定义在Resources/i18n/en.php中。任何消息都可以替换为自定义字符串,或翻译为其他语言。英文字符串始终在Factory实例化时加载。

根据失败类型,将提供各种变量以供使用,但是以下变量始终可用于所有消息

  • :attribute:正在验证的属性,如果设置了别名,则使用别名,
  • :value:正在验证的属性的值,已转换为字符串,数组对象作为JSON字符串。

加载翻译消息

默认情况下,只有英文消息由Factory类加载。在编写此文档时,已经由贡献者提供了德语翻译,但是可以通过创建一个返回包含消息键和新的消息字符串数组的PHP文件来添加任何语言。

要加载内置语言,必须在调用validate之前调用Factor::registerLanguageMessages()。例如:

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->registerLanguageMessages('de');

registerLanguageMessages有一个可选的第二个参数,允许指定语言文件的路径。如果没有提供,则将使用<vendor_dir>/src/Resources/i18n的库路径。如果您希望使用完全自定义的语言文件,请使用第二个参数提供您的文件。这可以是一个完全覆盖默认消息的英文语言文件。

例如

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->registerLanguageMessages('en', '/path/to/project/i18n/en_US.php');

可以多次调用以添加多种语言。

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->registerLanguageMessages('en', '/path/to/project/i18n/en_US.php');
$factory->registerLanguageMessages('es', '/path/to/project/i18n/es.php');
$factory->registerLanguageMessages('de', '/path/to/project/i18n/de.php');

Validator的自定义消息

所有消息都存储在Factory实例的MessageBag中。可以向该消息包添加更多语言,或在特定的验证实例上进行自定义。此外,可以在Factory的消息包上设置默认语言,或在验证实例上设置特定语言。

要添加一组新的消息:

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->messages()->add('es', [
    'rule.required' => 'Se requiere :attribute',
]);

$validation = $factory->validate($inputs, $rules);
$validation->setLanguage('es')->validate();

或覆盖默认的英文字符串:

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->messages()->replace('en', 'rule.required', 'Se requiere :attribute');

$validation = $factory->validate($inputs, $rules);
$validation->validate();

或设置默认语言:

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->messages()->default('es');

$validation = $factory->validate($inputs, $rules);
$validation->validate();

特定属性规则的自定义消息

有时您可能想要为特定属性规则设置自定义消息,以使其更明确或添加其他信息。这可以通过为属性添加带有:和规则名称的消息键来完成。

例如

use Somnambulist\Components\Validation\Factory;

$validator = new Factory();
$validation_a = $validator->make($input, [
	'age' => 'required|min:18'
]);

$validation->messages()->add('en', 'age:min', '18+ only');

$validation->validate();

有时您可能希望在使用错误消息时使用来自其他规则的参数。从版本1.6.0开始,您可以使用点符号来访问这些参数,然后是您想要使用的参数。例如:

一个password属性使用required|between:8,16|regex:/^[\\da-zA-Z!$%+.]+$/进行验证,但错误消息总是想引用最小/最大值。这可以这样完成:

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->messages()->replace('en', 'password:between', 'Your password must be between :min and :max characters and only [! $ % + .] as special characters.');
$factory->messages()->replace('en', 'password:regex', 'Your password must be between :between.min and :between.max characters and only [! $ % + .] as special characters.');

对于regex消息,通过在最小/最大值前加上between.来引用between的参数。并非所有规则都有参数,在这些情况下不会进行替换。

请注意,只能引用同一属性的规则参数。您不能访问完全不同的属性中的参数,例如:如果您验证了电子邮件或用户名,您将无法在密码上下文中访问这些参数。

规则的自定义消息

一些规则有几个可能的验证消息。它们都命名为rule.<name>.<check>。要更改消息,可以覆盖或添加特定的消息。

例如,uploaded_file可能对文件、最小/最大大小和类型有失败。这些都绑定到:

  • rule.uploaded_file
  • rule.uploaded_file.min_size
  • rule.uploaded_file.max_size
  • rule.uploaded_file.type

要更改任何子消息,请向消息包添加/覆盖该消息键。

例如

use Somnambulist\Components\Validation\Factory;

$validator = new Factory();
$validation_a = $validator->make($input, [
	'age' => 'required|min:18'
]);

$validation->messages()->add('en', 'age:min', '18+ only');

$validation->validate();

rakit不同,不能直接在Rule实例中设置自定义消息。任何消息都必须在消息包中设置。

复杂的翻译需求

此库中的翻译系统相当基础。如果您有复杂的需求,或希望处理可数等,那么所有错误消息都存储为包含消息键和该消息变量的ErrorMessage实例。

您可以使用底层的数组(或一个DataBag实例)来显示消息,而不是使用ErrorBag,然后传递消息键和变量到您的翻译系统中。

请注意,错误是按属性和规则名称嵌套的集合。

处理错误消息

错误消息收集在您可以通过验证实例上的errors()访问的ErrorBag实例中。

use Somnambulist\Components\Validation\Factory;

$validation = (new Factory())->validate($inputs, $rules);

$errors = $validation->errors();

现在,您可以使用以下方法检索消息:

all(string $format = ':message')

获取所有消息作为平铺数组。

$messages = $errors->all();
// [
//     'email is not a valid email address',
//     'password minimum is 6 characters',
//     'password must contain capital letters'
// ]

$messages = $errors->all('<li>:message</li>');
// [
//     '<li>email is not a valid email address</li>',
//     '<li>password minimum is 6 character</li>',
//     '<li>password must contain capital letters</li>'
// ]

首先(string $format = ':message', bool $dotNotation = false)

从所有现有键中获取第一条消息

$messages = $errors->firstOfAll();
// [
//     'email' => 'Email is not valid email',
//     'password' => 'Password minimum 6 character',
// ]

$messages = $errors->firstOfAll('<li>:message</li>');
// [
//     'email' => '<li>Email is not valid email</li>',
//     'password' => '<li>Password minimum 6 character</li>',
// ]

参数$dotNotation用于数组验证。如果它是false,则返回原始数组结构;如果它是true,则返回带有点表示法键的扁平化数组。

例如

$messages = $errors->firstOfAll(':message', false);
// [
//     'contacts' => [
//          1 => [
//              'email' => 'Email is not valid email',
//              'phone' => 'Phone is not valid phone number'
//          ],
//     ],
// ]

$messages = $errors->firstOfAll(':message', true);
// [
//     'contacts.1.email' => 'Email is not valid email',
//     'contacts.1.phone' => 'Email is not valid phone number',
// ]

first(string $key)

获取给定键的第一个消息。如果键有任何错误消息,则返回string;如果没有错误,则返回null

例如

if ($emailError = $errors->first('email')) {
    echo $emailError;
}

toArray()

获取ErrorMessage对象的原始底层关联数组。

例如

$messages = $errors->toArray();
// [
//     'email' => [
//         'email' => 'Email is not valid email'
//     ],
//     'password' => [
//         'min' => 'Password minimum 6 character',
//         'regex' => Password must contains capital letters'
//     ]
// ]

toDataBag()

将ErrorMessage对象的原始底层关联数组作为DataBag实例获取。

例如

$message = $errors->toDataBag()->filter()->first();

count()

获取错误消息的数量。

has(string $key)

检查给定的键是否有错误。如果键有错误,则返回true,否则返回false

注册/覆盖规则

默认情况下,所有内置规则都会自动注册到Factory实例。其中一些规则是内部需要的(例如requiredcallback);然而,你可以为验证添加任意数量的新规则到工厂中。

这是通过访问Factory实例上的addRule()方法并添加一个新的规则实例来完成的。

例如,你想创建一个检查数据库中字段可用性的unique验证器。

首先,让我们创建UniqueRule

<?php declare(strict_types=1);

use Somnambulist\Components\Validation\Rule;

class UniqueRule extends Rule
{
    protected string $message = ":attribute :value has been used";
    protected array $fillableParams = ['table', 'column', 'except'];
    protected PDO $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    public function check($value): bool
    {
        // make sure required parameters exists
        $this->assertHasRequiredParameters(['table', 'column']);

        // getting parameters
        $column = $this->parameter('column');
        $table = $this->parameter('table');
        $except = $this->parameter('except');

        if ($except && $except == $value) {
            return true;
        }

        // do query
        $stmt = $this->pdo->prepare(sprintf('select count(*) as count from %s where %s = :value', $table, $column));
        $stmt->bindParam(':value', $value);
        $stmt->execute();
        $data = $stmt->fetch(PDO::FETCH_ASSOC);

        // true for valid, false for invalid
        return intval($data['count']) === 0;
    }
}

现在要将此规则注册到Factory实例,需要将其添加到其中

use Somnambulist\Components\Validation\Factory;

$factory = new Factory();
$factory->addRule('unique', new UniqueRule($pdo));

现在你可以这样使用它

$validation = $factory->validate($_POST, [
    'email' => 'email|unique:users,email,exception@mail.com'
]);

在上面的UniqueRule中,属性$message用于无效消息。属性$fillableParams定义了规则参数的顺序和名称。默认情况下,fillParameters将从字符串规则填充列在$fillableParams中的参数。例如,上面的例子中的unique:users,email,exception@mail.com将设置

$params['table'] = 'users';
$params['column'] = 'email';
$params['except'] = 'exception@mail.com';

如果你想你的自定义规则接受参数列表,如innot_inuploaded_file规则,你需要在你的自定义规则类中重写fillParameters(array $params)方法。

请注意,我们上面创建的unique规则也可以这样使用

$validation = $factory->validate($_POST, [
    'email' => [
    	'required', 'email',
    	$factory('unique', 'users', 'email')
    ]
]);

你可以通过添加一些方法来改进上面的UniqueRule类,而不是使用字符串格式来设置参数

<?php

class UniqueRule extends Rule
{
    public function table(string $table): self
    {
        $this->params['table'] = $table;
        
        return $this;
    }

    public function column(string $column): self
    {
        $this->params['column'] = $column;
        
        return $this;
    }

    public function except(string $value): self
    {
        $this->params['except'] = $value;
        
        return $this;
    }
}

现在配置规则变为

$validation = $factory->validate($_POST, [
    'email' => [
    	'required', 'email',
    	$validator('unique')->table('users')->column('email')->except('exception@mail.com')
    ]
]);

隐式规则

隐式规则是一种规则,如果它无效,则忽略后续规则。例如,如果属性没有通过required*规则,则后续规则将无效。为了防止不必要的验证和错误消息,我们将required*规则设置为隐式。

要使你的自定义规则隐式,你可以将$implicit属性的值设置为true。例如

<?php
use Somnambulist\Components\Validation\Rule;

class YourCustomRule extends Rule
{
    protected bool $implicit = true;
}

修改值

在某些情况下,你可能希望你的自定义规则能够像default/defaults规则一样修改属性值。在当前和下一个规则检查中,将使用你修改的值。

为此,你应该实现Somnambulist\Components\Validation\Rules\Contracts\ModifyValue并在你的自定义规则类中创建modifyValue(mixed $value)方法。

例如

<?php

use Somnambulist\Components\Validation\Rule;
use Somnambulist\Components\Validation\Rules\Contracts\ModifyValue;

class YourCustomRule extends Rule implements ModifyValue
{
    public function modifyValue(mixed $value): mixed
    {
        // Do something with $value

        return $value;
    }
}

在验证之前钩子

在运行验证之前,你可能需要进行一些准备。例如,uploaded_file规则将解决来自$_FILES(不理想)数组的属性值,使其成为一个有组织的数组。

为此,你应该实现Somnambulist\Components\Validation\Rules\Contracts\BeforeValidate并在你的自定义规则类中创建beforeValidate()方法。

例如

<?php

use Somnambulist\Components\Validation\Rule;
use Somnambulist\Components\Validation\Rules\Contracts\BeforeValidate;

class YourCustomRule extends Rule implements BeforeValidate
{
    public function beforeValidate(): void
    {
        $attribute = $this->getAttribute();
        $validation = $this->validation;

        // Do something with $attribute and $validation
        // For example change attribute value
        $validation->setValue($attribute->getKey(), "your custom value");
    }
}

测试

使用PHPUnit 9+进行测试。通过vendor/bin/phpunit运行测试。

贡献

欢迎贡献力量!请复制仓库并提交PR。请确保您的代码使用PSR-12编码标准格式化,并且所有PHP文件在开头<?php标签处包含declare(strict_types=1);。如有任何代码风格规范的疑问,请查看现有文件并遵循。

该库目前针对PHP 8.0.X和8.1+。如果使用8.1函数,请确保使用合适的回退方案。注意,不要将外部库添加到该项目中。

添加新功能时,请确保更新README.md文件以反映您的更改,并包括适当的测试。如果可能的话,还应包括语言翻译,英语为首选。

对于错误修复,必须在测试中包含失败的案例。没有适当测试或无法复制的更改可能会被拒绝。