githusband/validation

Parameters Validation

v2.4.4 2024-04-27 08:14 UTC

This package is auto-updated.

Last update: 2024-05-08 14:15:45 UTC


README

English | 中文

Validation

简单,直观,客制化

68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f67697448757362616e642f56616c69646174696f6e 68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c616e6775616765732f746f702f67697448757362616e642f56616c69646174696f6e 68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f67697448757362616e642f56616c69646174696f6e2f756e69742d74657374732e796d6c3f6c6162656c3d756e69742d7465737473 68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f67697448757362616e642f56616c69646174696f6e

目录

Validation —— 一款 PHP 直观的数据验证器

Validation 用于对数据合法性的检查。 目标只有 10 个字 - 规则结构即是数据结构

github 仓库

期待任何形式的贡献,让我们一起开发或优化 Validation!谢谢!

🙋 为什么写这个工具?
  1. 对于API的参数,理论上对每个参数都应该进行合法性检查,尤其是那些需要转发给其他API接口或者需要存储到数据库的参数。 比如,数据库基本上对数据长度类型等有限制,对于长度的验证可谓是简单繁琐,使用该工具可以大大简化代码。
  2. 如果API参数过多,验证的代码量势必很大,无法直观通过代码明白参数格式。
  3. 只需定制一个验证规则数组,规则数组长啥样,请求参数就长啥样。
  4. 方便地多样化地设置验证方法返回的错误信息
  5. 暂时想不到,想到了再给你们编。 🐶

1. 简介

1.1 特点

  • 一个字段对应一个验证规则集,一个规则集由多个验证规则和方法(函数)组成。
  • 验证方法支持用标志代替,易于理解,简化规则。采用*, >, <, length> 等方法标志,比如 * 表示必要的
  • 支持正则表达式
👇 点击查看更多特性...
  • 支持方法传参。如 @this 代表当前字段值
  • 支持拓展方法
  • 支持串联验证:一个参数多个方法必须全部满足
  • 支持并联验证:一个参数多个规则满足其一即可
  • 支持条件验证:条件满足则继续验证后续方法,不满足则表明该字段是可选的
  • 支持无限嵌套的数据结构,包括关联数组,索引数组
  • 支持特殊的验证规则
  • 支持客制化配置。比如多个方法的分隔符号默认 |,可以改为其他字符,如 ;
  • 支持国际化。默认英语,支持自定义方法返回错误信息
  • 支持一次性验证全部数据(默认),也可设置参数验证失败后立即结束验证
  • 支持自定义错误信息,支持多种格式的错误信息,无限嵌套或者一维数组的错误信息格式
  • 暂时想不到,想到了再给你们编。 🐶

1.2 一个例子

use githusband\Validation;

// 待验证参数的简单示例。实际上无论参数多么复杂,都支持一个验证规则数组完成验证
$data = [
    "id" => 1,
    "name" => "Devin",
    "age" => 18,
    "favorite_animation" => [
        "name" => "A Record of A Mortal's Journey to Immortality",
        "release_date" => "July 25, 2020 (China)"
    ]
];

// 验证规则数组。规则数组的格式与待验证参数的格式相同。
$rule = [
    "id" => "required|/^\d+$/",         // 必要的,且只能是数字
    "name" => "required|length><=[3,32]",  // 必要的,且字符串长度必须大于3,小于等于32
    "favorite_animation" => [
        "name" => "required|length><=[1,64]",          // 必要的,且字符串长度必须大于1,小于等于64
        "release_date" => "optional|length><=[4,64]",  // 可选的,如果不为空,那么字符串长度必须大于4,小于等于64
    ]
];

$config = [];
// 实例化类,接受一个自定义配置数组,但不必要
$validation = new Validation($config);

// 设置验证规则并验证数据,成功返回 true,失败返回 false
if ($validation->set_rules($rule)->validate($data)) {
    // 这里获取验证结果,有被规则{$rule}验证到的参数,成功则修改其值为true,失败则修改其值为错误信息,
    // 没有被验证到的参数,保持原值不变。比如 age 保持 18 不变。
    return $validation->get_result();
} else {
    // 一共有四种错误信息格式可供选择。默认 Validation::ERROR_FORMAT_DOTTED_GENERAL
    return $validation->get_error();
}

理论上,该工具是用于验证复杂的数据结构的,但如果你想验证单一字符串,也可以,例如

$validation->set_rules("required|string")->validate("Hello World!");

2. 安装

$ composer require githusband/validation

3. 开发

如果你有想法优化开发本工具,以下将为你提供帮助:

src/Test 目录下已经内置了单元测试类 Unit.php 和 文档测试类 Readme.php 通过 Composer Script 调用它们。

克隆本项目后,请先加载项目的自动加载文件:

$ composer dump-autoload --dev
  • 单元测试类

这里包含了所有功能的测试,只包含部分内置函数。 原则上修改代码后跑一遍单元测试,确保功能正常。

// 测试所有例子,并打印 debug 信息。
$ VALIDATION_LOG_LEVEL=1 composer run-script test
// 测试单一例子,例如,测试正则表达式
$ composer run-script test test_regular_expression

如果你已经安装 Docker,那么,你可以一次性测试多个 PHP 版本: PHP v5.6, PHP v7.4.33PHP 最新版本

$ composer run-script multi-test
  • 文档测试类

文档代码:1.2 一个例子

// 验证成功测试, 返回测试结果
$ composer run-script readme test_simple_example

文档代码:附录 2 - 验证完整示例

// 验证成功测试,返回错误信息
$ composer run-script readme test_complete_example

4. 功能介绍

4.1 方法及其标志

一个字段对应一个验证规则集,一个规则集由多个验证 规则方法(函数)错误消息模板(可选的) 组成。 为了便于理解以及简化规则,允许采用一些方法 标志 代表实际的方法(函数)。

// 名字是必要的,必须是字符串,长度必须大于3且小于等于32
"name" => "required|string|length_greater_lessequal[3,32]"

// 采用函数标志,同上
// 若是觉得函数标志难以理解,请直接使用函数全称即可
"name" => "*|string|length><=[3,32]"

例如:

标志 方法 含义
* required 必要的,不允许为空
O optional 可选的,允许不设置或为空
>[20] greater_than 数字必须大于20
length><=[2,16] length_greater_lessequal 字符长度必须大于2且小于等于16
ip ip 必须是ip地址

完整的方法及其标志见 附录 1 - 方法标志及其含义

4.2 正则表达式

一般以 / 开头,以 / 结尾,表示是正则表达式 正则表达式最后面的 / 之后可能跟随着 模式修饰符,如 /i

// id 是必要的,且必须是数字
"id" => "required|/^\d+$/",

支持在一个串联规则中,同时使用多个正则表达式

4.3 方法传参

在以字符串书写的规则中,如何给方法传参呢?

  1. 标准参数 跟 PHP 函数使用参数一样,参数写在小括号()里面,多个参数以逗号,分隔,,前后不允许多余的空格 例如,
"age" => "equal(@this,20)"

表示 age 必须等于20。这里的 @this 代表当前 age 字段的值。

  1. 省略 @this 参数 当参数写在中括号[]里面时,首个 @this 参数可省略不写。 例如,上述例子可简写为:
"age" => "equal[20]"
  1. 省略参数 当函数参数只有一个且为当前字段值时,可省略 ()[] ,只写方法。

参数变量表

参数 描述
静态值 代表参数是静态字符串,允许为空。例如 20
@this 代表参数是当前字段的值
@parent 代表参数是当前字段的父元素的值
@root 代表参数是整个原始验证数据
@{field_name} 代表参数是整个验证数据中的字段名是 field_name 的字段的值。例如 @id

参数分隔符:

  • symbol_parameter_separator: , 将参数字符串分割为多个参数的符号。 例如:equal(@this,1) 以下特殊的情况,, 不会被视为参数分隔符:
    • \,
    • [] 包裹起来的。例如:my_method[[1,2,3],100] 表示这里有两个参数,数组 [1,2,3] 和 整形 100
    • {} 包裹起来的。
  • 自定义参数分隔符,见 4.10 客制化配置

参数类型: 自动探测参数的类型并强制转换为对应类型。

  1. 被双引号(")或单引号(')包含的内容即被视为字符串。否则将探测并强制转换类型。
  • 例如 "abc", 'abc' 或者 abc 都被视为字符串 abc
  1. 支持转换的类型有:
  • int:例如 123 为整形,"123" 为字符串
  • float: 例如 123.0
  • bool: 例如 false 或者 TRUE
  • array: 例如 [1,2,3]
  • object: 例如 {"a": "A", "b": "B"}
  • 例如: my_method[[1,"2",'3'],100,false,"true"]
    • [1,"2",'3'] 将被转换为 array([1,"2","3"])
    • 100 将被转换为 int(100)
    • false 将被转换为 bool(false)
    • "true" 将被转换为 string(true)

4.4 方法拓展

Validation 类中内置了一些验证方法,例如 *>, length>=, ip 等等。详见 附录 1 - 方法标志及其含义

如果验证规则比较复杂,内置方法无法满足你的需求,可以拓展你自己的方法。 如果方法中根据不同的判断可能返回不同的错误信息,见 4.13 错误信息模板 - 3. 在方法中直接返回模板 一节。

拓展方法有三种方式:

  1. 注册新的方法add_method
👇 点击查看代码
// 注册一个新的方法,check_id
$validation->add_method('check_id', function ($id) {
    if ($id == 0) {
        return false;
    }

    return true;
});

// 规则这么写
$rule = [
    // 必要的,且只能是数字,且必须大于 0
    "id" => "required|/^\d+$/|check_id",
];
  1. 拓展 Validation

拓展 Validation 类并重写内置方法或者增加新的内置方法。推荐用 trait

👇 点击查看代码
use githusband\Validation;

/**
 * 1. 推荐用 trait 拓展验证方法
 * 如果需要定义方法标志,将他们放在属性中,属性命名规则:“method_symbols_of_” + 类名(大驼峰转下划线)
 */
trait RuleCustome
{
    protected $method_symbols_of_rule_custome = [
        '=1' => 'euqal_to_1',
    ];

    protected function euqal_to_1($data)
    {
        return $data == 1;
    }
}

/**
 * 2. 拓展类,直接增加验证方法
 * 如果需要定义方法标志,将他们放在属性 method_symbols 中
 */
class MyValidation extends Validation
{
    use RuleCustome;

    protected $method_symbols = [
        ">=1" => "grater_than_or_equal_to_1",
    ];

    protected function grater_than_or_equal_to_1($data)
    {
        return $data >= 1;
    }
}

/**
 * 规则就这么写
 */
$rule = [
    // id 必要的,且必须大于等于 1
    "id" => "required|>=1",
    // parent_id 可选的,且必须等于 1
    "parent_id" => "optional|euqal_to_1",
];
    1. 全局函数 包括系统自带的函数和用户自定义的全局函数。

三种函数的优先级 add_method > 内置方法 > 全局函数

若方法都不存在,则报错:未定义

4.5 串联并联规则

  • 串联:一个参数的多个方法必须全部满足,标志是 |
"age" => "required|equal[20]"
  • 并联:一个参数的多个规则满足其一即可。 两种方法:
    • A. {字段名} + [or]
    • B. 在当前字段下增加唯一子字段 [or]

[or] 的标志是 [||] , 标志支持自定义,使用方法同 [or]

// 串联,身高单位是必须的,且必须是 cm 或者 m
"height_unit" => "required|<string>[cm,m]",
// A. 并联,规则可以这么写,[or] 可以替换成标志 [||]
"height[or]" => [
    // 若身高单位是厘米 cm, 则身高必须大于等于100,小于等于200 
    "required|=(@height_unit,cm)|>=<=[100,200]",
    // 若身高单位是米 m, 则身高必须大于等于1,小于等于2
    "required|=(@height_unit,m)|>=<=[1,2]",
]
// B. 并联,规则也可以这么写,标志 [||] 可以替换成 [or]
"height" => [
    "[||]" => [
        // 若身高单位是厘米 cm, 则身高必须大于等于100,小于等于200 
        "required|=(@height_unit,cm)|>=<=[100,200]",
        // 若身高单位是米 m, 则身高必须大于等于1,小于等于2
        "required|=(@height_unit,m)|>=<=[1,2]",
    ]
]

4.6 条件规则

When 条件规则

When 条件规则:给任意规则或方法增设条件,一般只有当条件满足时,才验证该方法,否则跳过该方法。

  • 用法:{任意规则或方法} + : + when()
  • 规则或方法包括: required, optional, optional_unset, 正则表达式任意方法(比如方法 >=
  • 两种条件规则:when()when_not(),条件写在小括号中间,目前仅支持一个条件
  • 条件规则支持自定义,见 4.10 客制化配置
  1. 一般地,只有当条件满足时,才验证该方法,否则跳过该方法。例如:
$rule = [
    "id" => "required|><[0,10]",
    // 当 id 小于 5 时,name 只能是数字且长度必须大于 2
    // 当 id 大于等于 5 时,name 可以是任何字符串且长度必须大于 2
    "name" => "/^\d+$/:when(<(@id,5))|length>[2]",
    // 当 id 不小于 5 时,age 必须小于等于 18
    // 当 id 小于 5 时,age 可以是任何数字
    "age" => "int|<=[18]:when_not(<(@id,5))",
];
  1. 特殊地,required, optional, optional_unset,这三个规则,可能需要验证字段是否非空。

下面,我以 required 为例,说明 when()when_not() 的用法。

  • 2.1 正条件的必要规则required:when()
  1. 如果条件成立,则验证 required 方法
  2. 如果条件不成立,说明该字段是可选的: 2.1. 若该字段为空,立刻返回验证成功; 2.2. 若该字段不为空,则继续验证后续方法
$rule = [
    // 特征是必要的,且只能是 height(身高) 或 weight(体重)
    "attribute" => "required|<string>[height,weight]",
    // 若属性是 height, 则 centimeter 是必要的,若不是 height,则是可选的。
    // 无论如何,若该值非空,则必须大于 180
    "centimeter" => "required:when(=(@attribute,height))|>[180]",
];
  • 2.2 否条件的必要规则required:when_not()
  1. 如果条件不成立,则验证 required 方法
  2. 如果条件成立,说明该字段是可选的: 2.1. 若该字段为空,立刻返回验证成功; 2.2. 若该字段不为空,则继续验证后续方法
$rule = [
    // 特征是必要的,且只能是 height(身高) 或 weight(体重)
    "attribute" => "required|<string>[height,weight]",
    // 若属性不是 weight, 则 centimeter 是必要的,若是 weight,则是可选的。
    // 无论如何,若该值非空,则必须大于 180
    "centimeter" => "required:when_not(=(@attribute,weight))|>[180]",
];

标准条件规则

标准条件规则的写法跟 PHP 语法差不多,if()!if()

  • 标准正条件if()
  1. 如果条件成立,则继续验证后续方法
  2. 如果条件不成立,则不继续验证后续方法
$rule = [
    // 特征是必要的,且只能是 height(身高) 或 weight(体重)
    "attribute" => "required|<string>[height,weight]",
    // 若属性是 height, 则 centimeter 是必要的,且必须大于 180
    // 若不是 height,则不继续验证后续规则,即 centimeter 为任何值都可以。
    "centimeter" => "if(=(@attribute,height))|required|>[180]",
];
  • 标准否条件!if()
  1. 如果条件不成立,则继续验证后续方法
  2. 如果条件成立,则不继续验证后续方法
$rule = [
    // 特征是必要的,且只能是 height(身高) 或 weight(体重)
    "attribute" => "required|<string>[height,weight]",
    // 若属性不是 weight, 则 centimeter 是必要的,且必须大于 180
    // 若是 weight,则不继续验证后续规则,即 centimeter 为任何值都可以。
    "centimeter" => "!if(=(@attribute,weight))|required|>[180]",
];

抱歉,标准条件规则暂不支持 elseelse if,将在后续版本中支持。

4.7 无限嵌套的数据结构

支持无限嵌套的数据结构,包括关联数组,索引数组

1. 无限嵌套的关联数组 验证数据怎么写,规则数组就怎么写。例如:

$data = [
    "id" => 1,
    "name" => "Johnny",
    "favourite_fruit" => [
        "name" => "apple",
        "color" => "red",
        "shape" => "circular"
    ]
];

// 若要验证上述 $data,规则可以这么写
$rule = [
    "id" => "required|/^\d+$/",
    "name" => "required|length>[3]",
    "favourite_fruit" => [
        "name" => "required|length>[3]",
        "color" => "required|length>[3]",
        "shape" => "required|length>[3]"
    ]
];

2. 无限嵌套的索引数组 索引数组字段的名称后面加上标志 .*,或者给索引数组字段加上唯一子元素 *

👇 点击查看代码
$data = [
    "id" => 1,
    "name" => "Johnny",
    "favourite_color" => [
        "white",
        "red"
    ],
    "favourite_fruits" => [
        [
            "name" => "apple",
            "color" => "red",
            "shape" => "circular"
        ],
        [
            "name" => "banana",
            "color" => "yellow",
            "shape" => "long strip"
        ],
    ]
];

// 若要验证上述 $data,规则可以这么写
$rule = [
    "id" => "required|/^\d+$/",
    "name" => "required|length>[3]",
    "favourite_color.*" => "required|length>[3]",
    "favourite_fruits.*" => [
        "name" => "required|length>[3]",
        "color" => "required|length>[3]",
        "shape" => "required|length>[3]"
    ]
];

// 也可以这么写
$rule = [
    "id" => "required|/^\d+$/",
    "name" => "required|length>[3]",
    "favourite_color" => [
        "*" => "required|length>[3]"
    ],
    "favourite_fruits" => [
        "*" => [
            "name" => "required|length>[3]",
            "color" => "required|length>[3]",
            "shape" => "required|length>[3]"
        ]
    ]
];

4.8 可选字段

  1. 一般的,对于一个叶子字段(无任何子字段),可以直接使用 optional 方法,表示该字段是可选的。
  2. 有时候,数组也是可选的,但是一旦设置,其中的子元素必须按规则验证。对于这种情况,只需要在数组字段名后面加上 [optional],表示该数组是可选的。
  3. 与在字段名后面加上 [optional] 一样的效果,给字段增加唯一子元素 [optional],也表示该字段是可选的。
  4. [optional] 的标志是 [O],两者可以互相替换。

例如:

$rule = [
    // 1. 叶子字段,直接使用 optional 方法,表示该字段是可选的
    "name" => "optional|string",
    // 2. 任意字段,在字段名后面添加 [optional],表示该字段是可选的
    "favourite_fruit[optional]" => [
        "name" => "required|string",
        "color" => "required|string"
    ],
    // 3. 任意字段,增加唯一子元素 [optional],表示该字段是可选的
    "gender" => [ "[optional]" => "string" ],
    "favourite_food" => [
        "[optional]" => [
            "name" => "required|string",
            "taste" => "required|string"
        ]
    ],
];

4.9 特殊的验证规则

特殊规则列表:

全称 标志 含义
[optional] [O] 表明字段是可选的,支持数组。见 4.8 可选字段
[or] [||] 表明单个字段是或规则,多个规则满足其一即可。见 4.5 串联并联规则
(无全称) .* 表明字段是索引数组。见 4.7 无限嵌套的数据结构

注意:标志使用方法和全称一样,且标志支持 客制化

4.10 客制化配置

支持客制化的配置有:

👇 点击查看配置
$config = [
    'language' => 'en-us',                                      // Language, Default en-us
    'lang_path' => '',                                          // Customer Language file path
    'validation_global' => true,                                // 1. true - validate all rules even though previous rule had been failed; 2. false - stop validating when any rule is failed
    'auto_field' => "data",                                     // If root data is string or numberic array, add the auto_field as the root data field name
    'reg_msg' => '/ >> (.*)$/',                                 // Set the error message format for all the methods after a rule string
    'reg_preg' => '/^(\/.+\/.*)$/',                             // If a rule match reg_preg, indicates it's a regular expression instead of method
    'reg_preg_strict' => '/^(\/.+\/[imsxADSUXJun]*)$/',         // Verify if a regular expression is valid
    'reg_ifs' => '/^!?if\((.*)\)/',                             // A regular expression to match both reg_if and reg_if_not
    'reg_if' => '/^if\((.*)\)/',                                // If match reg_if, validate this condition first, if true, then continue to validate the subsequnse rule
    'reg_if_not' => '/^!if\((.*)\)/',                           // If match reg_if_not, validate this condition first, if false, then continue to validate the subsequnse rule
    'symbol_rule_separator' => '|',                             // Serial rules seqarator to split a rule into multiple methods
    'symbol_parallel_rule' => '[||]',                           // Symbol of the parallel rule, Same as "[or]"
    'symbol_method_standard' => '/^([^\\(]*)\\((.*)\\)$/',      // Standard method format, e.g. equal(@this,1)
    'symbol_method_omit_this' => '/^([^\\[]*)\\[(.*)\\]$/',     // @this omitted method format, will add a @this parameter at first. e.g. equal[1]
    'symbol_parameter_separator' => ',',                        // Parameters separator to split the parameter string of a method into multiple parameters, e.g. equal(@this,1)
    'symbol_field_name_separator' => '.',                       // Field name separator of error message, e.g. "fruit.apple"
    'symbol_required' => '*',                                   // Symbol of required field, Same as the rule "required"
    'symbol_optional' => 'O',                                   // Symbol of optional field, can be not set or empty, Same as the rule "optional"
    'symbol_optional_unset' => 'O!',                            // Symbol of optional field, can be not set only, Same as the rule "optional_unset"
    'symbol_array_optional' => '[O]',                           // Symbol of array optional rule, Same as "[optional]"
    'symbol_index_array' => '.*',                               // Symbol of index array rule
    'reg_whens' => '/^(.+):(!)?\?\((.*)\)/',                    // A regular expression to match both reg_when and reg_when_not. Most of the methods are allowed to append a if rule, e.g. required:when, optional:when_not
    'reg_when' => '/^(.+):\?\((.*)\)/',                         // A regular expression to match a field which must be validated by method($1) only when the condition($3) is true
    'symbol_when' => ':?',                                      // We don't use the symbol to match a When Rule, it's used to generate the symbols in README
    'reg_when_not' => '/^(.+):!\?\((.*)\)/',                    // A regular expression to match a field which must be validated by method($1) only when the condition($3) is not true
    'symbol_when_not' => ':!?',                                 // We don't use the symbol to match a When Rule, it's used to generate the symbols in README
];

例如,你觉得我设计的规则太丑了,一点都不好理解。😡 于是你做了如下的定制:

$custom_config = [
    'reg_preg' => '/^Reg:(\/.+\/.*)$/',                         // If a rule match reg_preg, indicates it's a regular expression instead of method
    'symbol_rule_separator' => '&&',                            // Serial rules seqarator to split a rule into multiple methods
    'symbol_method_standard' => '/^(.*)#(.*)$/',                // Standard method format, e.g. equal(@this,1)
    'symbol_method_omit_this' => '/^(.*)~(.*)$/',               // @this omitted method format, will add a @this parameter at first. e.g. equal[1]
    'symbol_parameter_separator' => '+',                        // Parameters separator to split the parameter string of a method into multiple parameters, e.g. equal(@this,1)
    'symbol_field_name_separator' => '->',                      // Field name separator of error message, e.g. "fruit.apple"
    'symbol_required' => '!*',                                  // Symbol of required field, Same as the rule "required"
    'symbol_optional' => 'o?',                                  // Symbol of optional field, can be not set or empty, Same as the rule "optional"
];

$validation = new Validation($custom_config);

那么,1.2 一个例子 中的规则可以这么写:

$rule = [
    "id" => "!*&&Reg:/^\d+$/",          // 必要的,且只能是数字
    "name" => "!*&&length><=~3+32",        // 必要的,且字符串长度必须大于3,小于等于32
    "favorite_animation" => [
        "name" => "!*&&length><=~1+64",                // 必要的,且字符串长度必须大于1,小于等于64
        "release_date" => "o?&&length><=#@this+4+64",  // 可选的,如果不为空,那么字符串长度必须大于4,小于等于64
    ]
];

是不是变得更加漂亮了呢?😍 快来试试吧!

4.11 国际化

为不同的方法定制默认的错误消息模板。见 4.13 错误信息模板 - 第 3 点

国际化列表

语言 文件名 类名 别名
English(Default) EnUs.php EnUs en-us
Chinese ZhCn.php ZhCn zh-cn
  • 国际化文件名和类名都采用大驼峰命名方式。
  • 通过 4.10 客制化配置 修改默认错误信息模板。
  • 通过 set_language 方法修改默认错误信息模板。支持用类名或者别名作为参数。
// 在实例化类的时候加入配置
$validation_conf = [
    'language' => 'zh-cn',
];
$validation = new Validation($validation_conf);

// 或者调用接口
$validation->set_language('zh-cn'); // 将加载 ZhCn.php 国际化文件

创建你的国际化文件

  1. 创建文件 /MyPath/MyLang.php

如果方法有对应的标志,比如 equal 的标志是 =, 那么这里的键推荐用方法标志 =。 如果方法没有对应标志,那么就只能用方法名,比如 check_custom

<?php

class MyLang
{
    public $error_templates = [
        // 覆盖默认方法 = 的错误信息模板
        '=' => '@this must be equal to @p1(From MyLang)',
        // 新增方法 check_custom 的错误信息模板
        'check_custom' => '@this check_custom error!'
    ];
}
  1. 配置国际化文件的路径
$validation->set_config(['lang_path' => '/MyPath/'])->set_language('MyLang');

直接使用国际化对象 实际上,上面国际化文件的方法最终调用的是 custom_language 接口。

// 必须是对象
$MyLang = (object)[];
$MyLang->error_templates = [
    // 覆盖默认方法 = 的错误信息模板
    '=' => '@this must be equal to @p1(From MyLang)',
    // 新增方法 check_custom 的错误信息模板
    'check_custom' => '@this check_custom error!'
];

$validation->custom_language($MyLang, 'MyLang');

4.12 验证全部数据

默认地,即使某个字段验证失败,也会继续验证后续的全部数据。 可设置当任意字段验证失败后,立即结束验证后续字段。

// 在实例化类的时候加入配置
$validation_conf = [
    'validation_global' => false,
];
$validation = new Validation($validation_conf);

// 或者调用 set_validation_global 接口
$validation->set_validation_global(false);

4.13 错误信息模板

如何判定方法验证失败? 当且仅当方法 返回值 === true 时,表示验证成功,否则表示验证失败。

当一个字段验证失败,你可能希望

  • 为整个规则集设置一个错误信息模板
  • 为规则集中的每一个规则单独设置一个错误信息模板

那么,你有多个途径可以设置错误信息模板:

  1. 在规则集中设置一个统一的模板(普通字符串)
  2. 在方法中直接返回模板(普通字符串)
  3. 为每一个方法定制模板 3.1. 在 规则集 中设置临时模板(JSON字符串等) 3.2. 通过 国际化 设置默认的模板

模板优先级 从高到低:1 > 2 > 3

支持被单独设置模板的方法,要求:

  • 方法返回 false: 根据方法或标志匹配错误信息模板
  • 方法返回标志:根据返回的标志匹配错误信息模板 比如 TAG:=,根据 = 匹配错误信息模板

模板变量

变量 描述 例子
@this 当前字段 id 或者 favorite_animation.name
@method 当前方法 > 或者 greater_than
@p{x} 当前字段的第 x 个参数 @p1 代表第 1 个参数的值。比如 100
@t{x} 当前字段的第 x 个参数的类型 @t1 代表第 1 个参数的类型。比如 int

1. 在 规则集 中设置临时模板

临时模板只对当前规则集有效,对其他规则集无效。

1.1 在一个规则集的最后,加入标志 ">>", 注意前后各有一个空格。自定义标志见 4.10 客制化配置

1.1.1. 普通字符串:表示无论规则中的任何方法验证失败,都使用此错误信息 它对当前字段的所有规则都生效。无论方法返回 false 或者其他错误模板,都使用此错误信息

// required 或者 正则 或者 >=<= 方法,无论哪一个验证失败都报错 "id is incorrect."
"id" => 'required|/^\d+$/|>=<=[1,100] >> @this is incorrect.'

1.1.2. JSON 字符串:为每一个方法设置一个错误信息模板 它只对对应的规则都生效。如果方法返回 false,则使用对应的错误信息

"id" => 'required|/^\d+$/|>=<=[1,100] >> { "required": "Users define - @this is required", "preg": "Users define - @this should be \"MATCHED\" @preg"}'

当其中任一方法验证错误并返回 false,对应的报错信息为

  • required: Users define - id is required
  • /^\d+$/: Users define - id should be "MATCHED" /^\d+$/
  • >=<=: id must be greater than or equal to 1 and less than or equal to 100

1.1.3. 专属字符串(不推荐):为每一个方法设置一个错误信息模板,同 JSON

"id" => "required|/^\d+$/|>=<=[1,100] >> [required]=> Users define - @this is required [preg]=> Users define - @this should be \"MATCHED\" @preg"

1.2. 错误信息模板数组:为每一个方法设置一个错误信息模板,同 JSON

  • 0: 验证规则
  • error_message:错误信息模板数组

不允许包含任何其他键。

$rule = [
    "id" => [
        'required|/^\d+$/|>=<=[1,100]',
        'error_message' => [                        
            'required' => 'Users define - @this is required',
            'preg' => 'Users define - @this should be \"MATCHED\" @preg',
        ]
    ]
];

2. 在方法中直接返回模板

当且仅当方法 返回值 === true 时,表示验证成功,否则表示验证失败。

所以方法允许四种错误返回:

return false;
  • 返回标志 (支持国际化) 根据返回的标志匹配错误信息模板。比如标志 is_exclude_animal
return "TAG:is_exclude_animal";
  • 返回错误信息模板
return "I don't like mouse";
  • 返回错误信息模板数组,默认有两个字段,error_typemessage,可自行增加其他字段
return [
    "error_type" => "server_error",
    "message" => "I don't like snake",
    "extra" => "You scared me"
];
👇 点击查看完整方法返回示例
function check_animal($animal) {
    if ($animal == "") {
        return false;
    } else if ($animal == "mouse") {
        return "I don't like mouse";
    } else if ($animal == "snake") {
        return [
            "error_type" => "server_error",
            "message" => "I don't like snake",
            "extra" => "You scared me"
        ];
    } else if (!is_exclude_animals($animal)) {
        return "TAG:is_exclude_animal";
    } else if (is_fake_animals($animal)) {
        return [
            "error_type" => "server_error",
            "message" => "TAG:is_fake_animals"
        ];
    }

    return true;
}

3. 通过国际化设置默认模板

默认模板对所有规则集生效,但可能被临时模板替代。

4.11 国际化

4.14 错误信息格式

一共有四种不同的错误信息格式:

  • ERROR_FORMAT_NESTED_GENERAL: 'NESTED_GENERAL'
{
     "A": {
         "1": "error_msg_A1",
         "2": {
             "a": "error_msg_A2a"
         }
     }
}
  • ERROR_FORMAT_NESTED_DETAILED: 'NESTED_DETAILED' 这种格式和上面的格式类似,只是错误信息变成了数组,包含更多的错误信息。
  • ERROR_FORMAT_DOTTED_GENERAL: 'DOTTED_GENERAL'
{
     "A.1": "error_msg_A1",
     "A.2.a": "error_msg_A2a",
}
  • ERROR_FORMAT_DOTTED_DETAILED: 'DOTTED_DETAILED' 这种格式和上面的格式类似,只是错误信息变成了数组,包含更多的错误信息。

详见 附录 3 - 错误信息格式

附录 1 - 方法标志及其含义

👇 点击查看 附录 1 - 方法标志及其含义
标志 方法 错误消息模板
/ default @this 验证错误
.* index_array @this 必须是索引数组
:? when 在特定情况下,
:!? when_not 在非特定情况下,
* required @this 不能为空
*:? required:when 在特定情况下,@this 不能为空
*:!? required:when_not 在非特定情况下,@this 不能为空
O optional @this 永远不会出错
O:? optional:when 在特定情况下,@this 才能为空
O:!? optional:when_not 在非特定情况下,@this 才能为空
O! optional_unset @this 允许不设置,且一旦设置则不能为空
O!:? optional_unset:when 在特定情况下,@this 允许不设置,且一旦设置则不能为空。否则不能为空
O!:!? optional_unset:when_not 在非特定情况下,@this 允许不设置,且一旦设置则不能为空。否则不能为空
/ preg @this 格式错误,必须是 @preg
/ preg_format @this 方法 @preg 不是合法的正则表达式
/ call_method @method 未定义
= equal @this 必须等于 @p1
!= not_equal @this 必须不等于 @p1
== strictly_equal @this 必须严格等于 @t1(@p1)
!== not_strictly_equal @this 必须严格不等于 @t1(@p1)
> greater_than @this 必须大于 @p1
< less_than @this 必须小于 @p1
>= greater_equal @this 必须大于等于 @p1
<= less_equal @this 必须小于等于 @p1
>< greater_less @this 必须大于 @p1 且小于 @p2
><= greater_lessequal @this 必须大于 @p1 且小于等于 @p2
>=< greaterequal_less @this 必须大于等于 @p1 且小于 @p2
>=<= between @this 必须大于等于 @p1 且小于等于 @p2
<number> in_number_array @this 必须是数字且在此之内 @p1
!<number> not_in_number_array @this 必须是数字且不在此之内 @p1
<string> in_string_array @this 必须是字符串且在此之内 @p1
!<string> not_in_string_array @this 必须是字符串且不在此之内 @p1
length= length_equal @this 长度必须等于 @p1
length!= length_not_equal @this 长度必须不等于 @p1
length> length_greater_than @this 长度必须大于 @p1
length< length_less_than @this 长度必须小于 @p1
length>= length_greater_equal @this 长度必须大于等于 @p1
length<= length_less_equal @this 长度必须小于等于 @p1
length>< length_greater_less @this 长度必须大于 @p1 且小于 @p2
length><= length_greater_lessequal @this 长度必须大于 @p1 且小于等于 @p2
length>=< length_greaterequal_less @this 长度必须大于等于 @p1 且小于 @p2
length>=<= length_between @this 长度必须大于等于 @p1 且小于等于 @p2
int integer @this 必须是整型
/ float @this 必须是小数
/ string @this 必须是字符串
array is_array @this 必须是数组
/ bool @this 必须是布尔型
bool= bool_equal @this 必须是布尔型且等于 @p1
/ bool_str @this 必须是布尔型字符串
/ bool_string @this 必须是布尔型字符串
bool_string= bool_string_equal @this 必须是布尔型字符串且等于 @p1
/ email @this 必须是邮箱
/ url @this 必须是网址
/ ip @this 必须是IP地址
/ mac @this 必须是MAC地址
/ dob @this 必须是正确的日期
/ file_base64 @this 必须是正确的文件的base64码
/ file_base64:mime @this 文件类型必须是 @p1
/ file_base64:size @this 文件尺寸必须小于 @p2kb
/ uuid @this 必须是 UUID
/ oauth2_grant_type @this 必须是合法的 OAuth2 授权类型
date is_date @this 必须是格式正确的日期
/ date:format:@p1 @this 必须是日期且格式为 @p1
/ date:format:@p2 @this 必须是日期且格式为 @p2
/ date:format:@p3 @this 必须是日期且格式为 @p3
date= date_equal @this 日期必须等于 @p1
date!= date_not_equal @this 日期必须不等于 @p1
date> date_greater_than @this 日期必须大于 @p1
date>= date_greater_equal @this 日期必须大于等于 @p1
date< date_less_than @this 日期必须小于 @p1
date<= date_less_equal @this 日期必须小于等于 @p1
date>< date_greater_less @this 日期必须大于 @p1 且小于 @p2
date>=< date_greaterequal_less @this 日期必须大于等于 @p1 且小于 @p2
date><= date_greater_lessequal @this 日期必须大于 @p1 且小于等于 @p2
date>=<= date_between @this 日期必须在 @p1 和 @p2 之间

附录 2 - 验证完整示例

设想一下,如果用户数据如下,它包含关联数组,索引数组,我们要如何定制规则去验证它,如何做到简单直观呢?

$data = [
    "id" => 1,
    "name" => "GH",
    "age" => 18,
    "favorite_animation" => [
        "name" => "A Record of A Mortal's Journey to Immortality",
        "release_date" => "July 25, 2020 (China)",
        "series_directed_by" => [
            "",
            "Yuren Wang",
            "Zhao Xia"
        ],
        "series_cast" => [
            [
                "actor" => "Wenqing Qian",
                "character" => "Han Li",
            ],
            [
                "actor" => "ShiMeng-Li",
                "character" => "Nan Gong Wan",
            ],
        ]
    ]
];
👇 点击查看代码
// $data - 上述待验证的数据 
function validate($data) {
    // 设置验证规则
    $rule = [
        "id" => "required|/^\d+$/",         // id 是必要的,且只能是数字
        "name" => "required|length><=[3,32]",  // name 是必要的,且字符串长度必须大于3,小于等于32
        "favorite_animation" => [
            // favorite_animation.name 是必要的,且字符串长度必须大于1,小于等于64
            "name" => "required|length><=[1,16]",
            // favorite_animation.release_date 是可选的,如果不为空,那么字符串长度必须大于4,小于等于64
            "release_date" => "optional|length><=[4,64]",
            // "*" 表示 favorite_animation.series_directed_by 是一个索引数组
            "series_directed_by" => [
                // favorite_animation.series_directed_by.* 每一个子元素必须满足其规则:不能为空且长度必须大于 3
                "*" => "required|length>[3]"
            ],
            // [optional] 表示 favorite_animation.series_cast 是可选的
            // ".*"(同上面的“*”) 表示 favorite_animation.series_cast 是一个索引数组,每一个子元素又都是关联数组。
            "series_cast" => [
                "[optional].*" => [
                    // favorite_animation.series_cast.*.actor 不能为空且长度必须大于 3且必须满足正则
                    "actor" => "required|length>[3]|/^[A-Za-z ]+$/",
                    // favorite_animation.series_cast.*.character 不能为空且长度必须大于 3
                    "character" => "required|length>[3]",
                ]
            ]
        ]
    ];
    
    $config = [
        'language' => 'zh-cn'
    ];
    // 实例化类,接受一个自定义配置数组,但不必要
    $validation = new Validation($config);

    // 设置验证规则并验证数据,成功返回 true,失败返回 false
    if ($validation->set_rules($rule)->validate($data)) {
        // 这里获取验证结果,有被规则{$rule}验证到的参数,成功则修改其值为true,失败则修改其值为错误信息,
        // 没有被验证到的参数,保持原值不变。比如 age 保持 18 不变。
        return $validation->get_result();
    } else {
        // 一共有四种错误信息格式可供选择。默认 Validation::ERROR_FORMAT_DOTTED_GENERAL
        return $validation->get_error();
    }
}

// 可以通过改变 get_error 的参数,找到适合自己的报错格式
// 例子中的 $data 基本都不满足 $rule ,可以改变 $data 的值,检测验证规则是否正确
echo json_encode(validate($data), JSON_PRETTY_PRINT + JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE) . "\n";

打印结果为

{
    "name": "name 长度必须大于 3 且小于等于 32",
    "favorite_animation.name": "favorite_animation.name 长度必须大于 1 且小于等于 16",
    "favorite_animation.series_directed_by.0": "favorite_animation.series_directed_by.0 不能为空",
    "favorite_animation.series_cast.1.actor": "favorite_animation.series_cast.1.actor 格式错误,必须是 /^[A-Za-z ]+$/"
}

更多的错误信息格式,见 附录 3 - 错误信息格式

附录 3 - 错误信息格式

一维字符型结构

// 默认 Validation::ERROR_FORMAT_DOTTED_GENERAL
$validation->get_error();
{
    "name": "name 长度必须大于 3 且小于等于 32",
    "favorite_animation.name": "favorite_animation.name 长度必须大于 1 且小于等于 16",
    "favorite_animation.series_directed_by.0": "favorite_animation.series_directed_by.0 不能为空",
    "favorite_animation.series_cast.1.actor": "favorite_animation.series_cast.1.actor 格式错误,必须是 /^[A-Za-z ]+$/"
}

一维关联型结构

$validation->get_error(Validation::ERROR_FORMAT_DOTTED_DETAILED);
{
    "name": {
        "error_type": "validation",
        "message": "name 长度必须大于 3 且小于等于 32"
    },
    "favorite_animation.name": {
        "error_type": "validation",
        "message": "favorite_animation.name 长度必须大于 1 且小于等于 16"
    },
    "favorite_animation.series_directed_by.0": {
        "error_type": "required_field",
        "message": "favorite_animation.series_directed_by.0 不能为空"
    },
    "favorite_animation.series_cast.1.actor": {
        "error_type": "validation",
        "message": "favorite_animation.series_cast.1.actor 格式错误,必须是 /^[A-Za-z ]+$/"
    }
}

无限嵌套字符型结构

$validation->get_error(Validation::ERROR_FORMAT_NESTED_GENERAL);
{
    "name": "name 长度必须大于 3 且小于等于 32",
    "favorite_animation": {
        "name": "favorite_animation.name 长度必须大于 1 且小于等于 16",
        "series_directed_by": [
            "favorite_animation.series_directed_by.0 不能为空"
        ],
        "series_cast": {
            "1": {
                "actor": "favorite_animation.series_cast.1.actor 格式错误,必须是 /^[A-Za-z ]+$/"
            }
        }
    }
}

无限嵌套关联型结构

$validation->get_error(Validation::ERROR_FORMAT_NESTED_DETAILED);
{
    "name": {
        "error_type": "validation",
        "message": "name 长度必须大于 3 且小于等于 32"
    },
    "favorite_animation": {
        "name": {
            "error_type": "validation",
            "message": "favorite_animation.name 长度必须大于 1 且小于等于 16"
        },
        "series_directed_by": [
            {
                "error_type": "required_field",
                "message": "favorite_animation.series_directed_by.0 不能为空"
            }
        ],
        "series_cast": {
            "1": {
                "actor": {
                    "error_type": "validation",
                    "message": "favorite_animation.series_cast.1.actor 格式错误,必须是 /^[A-Za-z ]+$/"
                }
            }
        }
    }
}