affinity4 / validate
使用魔术方法自动使用注解验证属性的特性
Requires
- nette/schema: ^1.2
- phpdocumentor/reflection-docblock: ^5.3
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-09-26 18:22:20 UTC
README
Affinity4 Validate 是一个特性,可以被添加到任何类中,以启用受保护的/private 属性验证。
一旦 Affinity4 validate 被添加到一个类中,任何私有或受保护的属性都可以通过添加 @validation docblock 标签来关联复杂的验证。
use Affinity4\Validate\Validate; class ClassWithValidation { use Validate; /** * @validation type(int:unsigned) */ protected $id; /** * @validation type(string:alphanumeric) */ protected $username; } $Validate = new ClassWithValidation; $Validate->id = null $Validate->username = 'not_alphanumeric123'; if (!$Validate->isValid()) { /* The validation errors are grouped by property... $validations_errors = [ 'id' => ValidationError([ [ "class" => "\ClassWithValidation", "property" => "id" "value" => null, "error" => "Must be of type string" ] ]), 'username => ValidationError([ [ "class" => "\ClassWithValidation", "property" => "username", 'value' => "not_alphanumeric123", 'error' => "Must be alphanumeric (letters and numbers only)" ] ]) ] */ foreach ($Validate->getValidationErrors() as $ValidationErrors) { /* NOTE: $ValidationErrors is an instance of \Affinity4\Validate\ValidationErrors See section ValidationErrors object */ } }
为什么只有私有和受保护的属性?
Affinity4 Validate 使用 __set 魔术方法来验证每个具有 @validation 注释的属性。这意味着只有私有/受保护的属性可以被验证。
验证器
@validation 有三个部分
type($type:$validator|$validator)
$type 验证将首先发生。这将是一个简单的类型检查(例如,is_int、is_string 等)
$validators 是一个管道分隔的验证列表,在 $type 验证器之后执行,除了 cast,它更像是一个中间件,在验证之前尝试将值转换为正确的类型。
String
验证属性值是否为字符串。注意:整数将不会通过此验证。可转换类型(例如,具有 __toString 的对象)将不会通过此验证,除非 'cast' 被用作验证器。请参阅下面的转换部分。
@validation type(string)
Int
验证属性值是否为整数。注意:数字字符串将不会通过此验证。可转换类型(例如,返回整数的可调用对象)将不会通过此验证,除非 'cast' 被用作验证器。请参阅下面的转换部分。
@validation type(int)
Numeric
验证值是否为数字。
/** * Numeric * * @validation type(numeric) * * @var numeric */ protected $numeric;
Alpha
注意:仅字符串
验证字符串是否仅包含字母字符(没有数字或符号)
/** * Alpha * * @validation type(string:alpha) * * @var string */ protected $alpha;
Alnum/Alphanum/Alphanumeric
注意:仅字符串
验证字符串是否只包含字母数字字符(仅数字和字母)
@validation type(string:alnum) // or @validation type(string:alphanum) // or @validation type(string:alphanumeric)
snakecase
注意:仅字符串
验证字符串是否是 snake_case
@validation type(string:snakecase)
kebabcase
注意:仅字符串
验证字符串是否是 kebab-case
@validation type(string:kebabcase)
constantcase/uppersnakecase
注意:仅字符串
验证字符串是否是 CONSTANT_CASE(也称为 UPPER_SNAKE_CASE)
@validation type(string:constantcase) // or @validation type(string:uppersnakecase)
camelcase
注意:仅字符串
验证字符串是否是 camelCase
@validation type(string:camelcase)
pascalcase/camelcaps/studlycaps/capitalcase
注意:仅字符串
验证字符串是否是 PascalCase(也称为 CamelCaps、也称为 StudlyCaps、也称为 CapitalCase)
@validation type(string:pascalcase) // or @validation type(string:camelcaps) // or @validation type(string:studlycaps) // or @validation type(string:capitalcase)
traincase
注意:仅字符串
验证字符串是否是 Train-Case
@validation type(string:traincase)
Unsiged
验证整数是否为正数,大于 0
@validation type(int:unsigned)
Cast
注意:仅字符串和整数
如果可能,将尝试将无效的值转换为正确的类型
这将检查对象是否具有 __toString 方法,或者尝试检索可调用对象的返回值作为所需类型。
注意:转换发生在任何验证之前。您可以将它视为一个中间件。
转换为 int
type(int:cast)
转换为字符串
type(string:cast)
/* * @validation type(int:unsigned|cast) */ protected $id; /* * @validation type(string:alnum|cast) */ protected $username; // ... class User { // ... public function __toString() { return 'user' . $this->user_id; } } $Class->username = new User; // "user001";
Match
仅字符串
根据提供的正则表达式模式匹配值
注意
- 不要在模式周围加引号
- 不要使用正则表达式分隔符。内部使用的默认分隔符是 /. 如果您需要更改此分隔符,您应使用 addValidationRule() 方法创建自定义验证规则
/** * Mobile * * Matches an Irish mobile number: * +3538123456789 * 003538123456789 * +353 81 234 5678 * 00353 81 234 5678 * * @validation match(^(?:0|(?:00|\+)353)\s*8\d{1}\s*\d{3}\s*\d{4}) * * @var string */ protected $mobile;
替换
将尝试用替换字符串替换匹配的模式。
注意
- 不要引用模式或替换字符串
- 不要使用正则表达式分隔符。内部使用的默认分隔符是 /. 如果您需要更改此分隔符,您应使用 addValidationRule() 方法创建自定义验证规则
- 使用 preg_replace 内部
- 您 不能 将数组作为替换值传递。仅允许字符串。
- 您 可以 使用变量占位符,就像在 preg_replace 中一样。例如,要加密信用卡号,您可以使用 replace((\d{4})\s*(\d{4})\s*(\d{4}), **** **** ${3}) // 返回:**** **** 1234
/** * Credit Card Number * * Matches an a credit card number (e.g. 1234 1234 1234) and encrypts it (e.g **** **** 1234): * * @validation replace((\d{4})\s*(\d{4})\s*(\d{4}), **** **** ${3}) * * @var string */ protected $credit_card_number;
ValidationErrors 类
每个错误组都包含在ValidationErrors类中。这样做的目的是为了更方便地访问特定的错误及其键,而不仅仅是简单地遍历验证错误数组。
例如,想象一个具有多个验证的属性,比如添加了自定义验证的密码。
- 验证长度必须大于8个字符。
- 验证至少有一个大写字母。
- 验证至少有一个数字。
如果没有ValidationErrors类,验证将被分组成一个数组,如下所示:
[ "password" => [ [ "class" => "\Acme\ClassWithValidation", "property" => "password", "value" => "password", "error" => "Password length must be greater than 8 characters" ], [ "class" => "\Acme\ClassWithValidation", "property" => "password", "value" => "password", "error" => "Password must have at least one capital letter" ], [ "class" => "\Acme\ClassWithValidation", "property" => "password", "value" => "password", "error" => "Password must have at least one number" ] ] ]
然后你需要进行循环,或者检查有多少个错误,甚至确定是否需要进行循环,并且你仍然必须使用索引处理所有情况。
$errors['password'][0]['error'] // Password length must be greater than 8 characters $errors['password'][3]['error'] // Ooops: There's no index 3, but a very easy and common mistake to make // Or what if you just wanted all the password errors? $password_errors = [] foreach ($errors['password'] as $error) { $password_errors[] = $error['error']; }
有了包含数组的ValidationErrors类,我们有许多有用的方法可以遍历数组,并获取我们想要的确切数据,而无需使用循环或数组键。
$password_errors = $Validate->getValidationErrors('password')->errors(); /* $password_errors = [ "Password length must be greater than 8 characters", "Password must have at least one capital letter", "Password must have at least one number" ] */
你也可以通过first()、next()、prev()和last()方法轻松地浏览验证错误。这些方法将通过属性(而不是键)公开数组项。
$Stub->getValidationErrors('password')->count(); // 3 $PasswordValidationErrors = $Stub->getValidationErrors('password'); $PasswordValidationErrors->first()->error; // Password length must be greater than 8 characters $PasswordValidationErrors->next()->error; // Password must have at least one capital letter $PasswordValidationErrors->prev()->error; // Password length must be greater than 8 characters $PasswordValidationErrors->last()->error; // Password must have at least one number // We also can get class, property and value $PasswordValidationErrors->first()->class; // \Acme\Validate $PasswordValidationErrors->first()->property; // 'password' $PasswordValidationErrors->first()->value; // 'password'
重要:我们必须将ValidationError实例存储在变量中,才能使用first、next、prev和last方法,因为每次调用getValidationErrors($key)都会返回一个新的ValidationError实例。
待办事项
-
添加类型(string:traincase) Train-Case
-
添加类型(string:uppercase)
-
添加类型(string:lowercase)
-
添加类型(string:flatcase)
-
添加类型(string:upperflatcase)
-
添加类型(string:hex) 验证器。验证字符串是否为十六进制值。ctype_xdigit($value)
-
添加类型(string:no_whitespace) 验证器。验证字符串没有空格(例如 \r\n\t)。!ctype_space($value) && ctype_print($value)
-
添加到(snakecase)
-
添加到(kebabcase)
-
添加到(constantcase) 即 macrocase/uppersnakecase
-
添加到(macrocase) 即 constantcase/uppersnakecase
-
添加到(uppersnakecase) 即 constantcase/macrocase
-
添加到(cobolcase) 即 upperkebabcase
-
添加到(upperkebabcase) 即 cobolcase
-
添加到(camelcase)
-
添加到(camelcaps) 即 pascalcase
-
添加到(pascalcase) 即 camelcaps
-
添加到(uppercase)
-
添加到(lowercase)
-
添加到(flatcase)
-
添加到(upperflatcase)
-
允许多个验证通过,例如type(string:any(kebabcase, snakecase))。注意:在any()内部不允许使用验证“函数”(例如regex($pattern))。相反,应使用addValidationRule()创建自定义验证器,并在any()内部使用该名称。
-
允许使用any()来允许多个有效类型,例如type(any(string,int,null))。注意:在这种情况下不允许使用其他验证器,例如type(any(string,null):cast|kebabcase),因为允许多个类型可能会使验证场景复杂化,并可能导致意外结果。
-
添加链式/流畅接口
@validation type(string:cast|kebabcase).to(snakecase) // Fails if not a string formatted as kebabcase. Otherwise converts kebabcase to snakecase (e.g. "i-am-a-kebab" to "i_am_a_kebab") // or @validation match(\s+).to(lowercase|snakecase) // Fails if not a sentence (no spaces found). Otherwise, converts sentences to lowercase snakecase (e.g. "I Am A Title" to "i_am_a_title") // or @validation match(\d{4}\s*\s{4}\s*\d{4}).replace((\d{4})\s*(\d{4})\s*(\d{4}), **** **** ${3}) // Fails if not a credit card number. Otherwise, encrypts it (e.g. **** **** 1234)