affinity4/validate

使用魔术方法自动使用注解验证属性的特性

0.0.1 2022-05-17 08:47 UTC

This package is auto-updated.

Last update: 2024-09-26 18:22:20 UTC


README

Affinity4

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

仅字符串
根据提供的正则表达式模式匹配值

注意

  1. 不要在模式周围加引号
  2. 不要使用正则表达式分隔符。内部使用的默认分隔符是 /. 如果您需要更改此分隔符,您应使用 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;

替换

将尝试用替换字符串替换匹配的模式。

注意

  1. 不要引用模式或替换字符串
  2. 不要使用正则表达式分隔符。内部使用的默认分隔符是 /. 如果您需要更改此分隔符,您应使用 addValidationRule() 方法创建自定义验证规则
  3. 使用 preg_replace 内部
  4. 不能 将数组作为替换值传递。仅允许字符串。
  5. 可以 使用变量占位符,就像在 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类中。这样做的目的是为了更方便地访问特定的错误及其键,而不仅仅是简单地遍历验证错误数组。

例如,想象一个具有多个验证的属性,比如添加了自定义验证的密码。

  1. 验证长度必须大于8个字符。
  2. 验证至少有一个大写字母。
  3. 验证至少有一个数字。

如果没有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实例。

待办事项

  1. 添加类型(string:traincase) Train-Case

  2. 添加类型(string:uppercase)

  3. 添加类型(string:lowercase)

  4. 添加类型(string:flatcase)

  5. 添加类型(string:upperflatcase)

  6. 添加类型(string:hex) 验证器。验证字符串是否为十六进制值。ctype_xdigit($value)

  7. 添加类型(string:no_whitespace) 验证器。验证字符串没有空格(例如 \r\n\t)。!ctype_space($value) && ctype_print($value)

  8. 添加到(snakecase)

  9. 添加到(kebabcase)

  10. 添加到(constantcase) 即 macrocase/uppersnakecase

  11. 添加到(macrocase) 即 constantcase/uppersnakecase

  12. 添加到(uppersnakecase) 即 constantcase/macrocase

  13. 添加到(cobolcase) 即 upperkebabcase

  14. 添加到(upperkebabcase) 即 cobolcase

  15. 添加到(camelcase)

  16. 添加到(camelcaps) 即 pascalcase

  17. 添加到(pascalcase) 即 camelcaps

  18. 添加到(uppercase)

  19. 添加到(lowercase)

  20. 添加到(flatcase)

  21. 添加到(upperflatcase)

  22. 允许多个验证通过,例如type(string:any(kebabcase, snakecase))。注意:在any()内部不允许使用验证“函数”(例如regex($pattern))。相反,应使用addValidationRule()创建自定义验证器,并在any()内部使用该名称。

  23. 允许使用any()来允许多个有效类型,例如type(any(string,int,null))。注意:在这种情况下不允许使用其他验证器,例如type(any(string,null):cast|kebabcase),因为允许多个类型可能会使验证场景复杂化,并可能导致意外结果。

  24. 添加链式/流畅接口

    @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)