marwanalsoltany / mighty
你需要的最后一个验证库!
Requires
- php: >=8.1
- ext-ctype: *
- ext-dom: *
- ext-intl: *
- ext-json: *
- ext-mbstring: *
Requires (Dev)
- code-lts/doctum: ^5.5
- friendsofphp/php-cs-fixer: ^3.9
- phpbench/phpbench: ^1.2
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-09-09 18:59:05 UTC
README
你需要的最后一个验证库!
如果你喜欢这个项目,并希望支持其开发,给它一个⭐会非常感激!
关键特性
- 无依赖。
- 框架无关,可以集成到任何代码库中。
- 表达性强、直观,易于使用,超过250个内置验证规则。
安装
composer require marwanalsoltany/mighty
关于Mighty
验证是任何Web应用程序中的常见任务。通过表单或任何类型的输入传递给应用程序的数据必须始终与一系列规则进行验证。Mighty可以以简单和表达的方式处理。
Mighty是一个快速、强大、健壮且易于使用的验证库,工作起来非常有趣,使验证任何数据变得轻而易举。得益于Mighty验证表达式语言(mVEL)的强大功能,它不像你之前见过的任何东西。凭借其验证方法和超过250个内置规则,几乎没有什么你不能用它验证的,以非常表达和紧凑的方式。简单来说,Mighty是强化版的验证!它确实是你需要用到的最后一个验证库。
Mighty提供了多种不同的方法来验证数据。它最常用的用例是通过HTTP请求验证传入的数据,但当然不仅限于这一点;Mighty还提供了一些属性作为约束,以方便验证模型和/或任何类型的对象。
Mighty包括一系列方便的验证规则,你可以将它们作为单个规则应用,或者使用运算符将它们组合起来,以构建更复杂的验证。
快速开始
要了解Mighty强大的验证功能,让我们直截了当地看看一些示例。
通用数据验证
使用 Validator::class
验证表单数据
use MAKS\Mighty\Validator; $validator = new Validator(); $validator ->setData([ 'name' => 'John Doe', 'username' => 'john.doe', 'password' => 'Super@Secret#123', 'email' => 'john@doe.com', 'hobbies' => ['coding', 'design', 'sports'], ]) ->setValidations([ // required&string&between:3,255 'name' => $validator->validation()->required()->string()->between(3, 255), // required&string&matches:/[a-z0-9._-]/i 'username' => $validator->validation()->required()->string()->matches('/[a-z0-9._-]/i'), // required&string&min:8 'password' => $validator->validation()->required()->string()->min(8), // required&email 'email' => $validator->validation()->required()->email(), // null^(required&array&max:5) 'hobbies' => $validator ->validation() ->null() ->xor() ->group(fn ($validation) => $validation ->array() ->max(5) ), // null|(if:${hobbies.validations.array}&(string&min:3)) // hobby can be null or a string with at least 3 characters if hobbies is an array 'hobbies.*' => $validator ->validation() ->null() ->or() ->group(fn ($validation) => $validation ->if('${hobbies.validations.array}') ->open() ->string() ->min(3) ->close() ), ]) ->validate(); $result = $validator->isOK(); // boolean result of the overall validation result $errors = $validator->getErrors(); // an array of results of validations that failed $results = $validator->getResults(); // an array of results of all validations $validator->check(); // void or throws an exception with a nicely formatted message of what exactly went wrong
对象验证
使用 Constraint::class
属性验证对象的状态
use MAKS\Mighty\Validation\Strategy; use MAKS\Mighty\Validation\Behavior; use MAKS\Mighty\Validation\Operator; use MAKS\Mighty\Validation\Constraint; use MAKS\Mighty\Validation\Constraint as Assert; use MAKS\Mighty\Validation\Constraint\ValidatableObjectInterface; use MAKS\Mighty\Validation\Constraint\ValidatableObjectTrait; class ValidatableObject implements ValidatableObjectInterface { use ValidatableObjectTrait; #[Assert\Rule\Equals('CONST')] public const CONST = 'CONST'; #[Assert\Rule\In(['STATIC', 'VAR'])] public static $static = 'VAR'; #[Assert\Rule\StringConstraint] #[Assert\Rule\StringCharset('UTF-8')] #[Assert\Rule\Between(3, 99)] public $default = 'DEFAULT'; #[Assert\Rule\StringConstraint] #[Assert\Rule\StringContains('<element>')] #[Assert\Rule\Xml] public $xml = '<?xml version="1.0"?><element></element>'; #[Assert\Rule\ArrayConstraint] #[Assert\Shape([ 'string' => new Assert\Rule\Str, 'array' => new Assert\Rule\Arr, ])] public $array = [ 'string' => 'value', 'array' => [], ]; #[Assert\Rule\ObjectConstraint] #[Assert\Rule\ObjectIsInstanceOf(ValidatableObjectInterface::class)] #[Assert\Valid(message: 'Not valid')] public $object; #[Assert\Callback('is_scalar', 'Data is not scalar')] #[Constraint('string&min:3', strategy: Strategy::FailLazy, messages: [ 'string' => 'Must be string.', 'min' => 'Must be longer than ${@arguments.0}.', ])] public function getDefault() { return $this->default; } #[Assert\Compound([ new Assert\Rule\Str, new Assert\Compound([ new Assert\Rule\Arr, new Assert\Compound([ new Assert\Rule\Blank, ], Operator::Not), ], Operator::And), ], Operator::Xor, Behavior::Pessimistic, Strategy::FailLazy)] public static function getStaticProperty() { return static::$static; } } $object = new ValidatableObject(); $result = $object->isValid(); // boolean result of the overall validation result $results = $object->validate(); // an array of results of all validations $object->check(); // void or throws an exception with a nicely formatted message of what exactly went wrong
验证可验证对象的一个示例输出可能看起来像这样
// check out the previous snippet see the used constraints $object = new ValidatableObject(); $object->object = new class implements ValidatableObjectInterface { use ValidatableObjectTrait; // some properties and their validation constraints ... }; $object->default = null; // this must be a string $object->check(); // ValidationFailedException::class // Data failed to pass the validation. // (01) The value (null) of the "ValidatableObject->default" property failed to pass the validation [string]. Problem: Value must be a string. // (02) The value (null) of the "ValidatableObject->default" property failed to pass the validation [string.charset:"UTF-8"]. Problem: Value must be encoded in one of the following charsets: ["UTF-8"]. // (03) The value (null) of the "ValidatableObject->default" property failed to pass the validation [between:3,99]. Problem: Value must be between 3 and 99 or have a value/count/length that is between 3 and 99. // (04) The return value (null) of the "ValidatableObject->getDefault()" method failed to pass the validation [callback]. Problem: Data is not scalar. // (05) The return value (null) of the "ValidatableObject->getDefault()" method failed to pass the validation [string&min:3]. Problems: Must be string; Must be longer than 3.
还可以查看ValidatableObject
和 ValidatableObjectChild
。
提示: 更多示例可以在示例部分找到。
Mighty验证表达式语言
mighty 有验证表达式这个概念。验证表达式最简单的形式就是描述 mighty 如何验证给定数据的字符串。这些字符串基于 mighty 验证表达式语言规范 (mVEL)。mVEL 非常简单,易于阅读和处理。它是基于一些已建立的概念和/或规范,如 布尔代数、位运算符、JSON 和 CSV。
因此,验证表达式可以定义为一个包含一些规则的字符串,这些规则由 位运算符 分隔,这些规则将通过使用 布尔代数 逻辑进行评估,最终得到验证的结果。规则可以有参数,这些参数的类型可以使用与 JSON 类型相同的规则表示。一个规则也可以有多个参数,这些参数由逗号 (CSV) 分隔。
例如, required&string&between:2,255|null
是一个有效的验证表达式,这个表达式可以理解为以下内容
- 表达式包含四个规则。
- 表达式包含以下规则
required
声明输入必须存在。string
声明输入是一个字符串。between:2,255
声明输入是一个长度在 2 到 255 之间的字符串。null
声明输入为 null。
- 最终结果是评估每个规则的结果,这些结果使用位运算符连接在一起。
required&string&between:2,255|null
表达式意味着输入必须存在;AND 类型为字符串;AND 长度在 2 到 255 之间;OR null。因此,它是一个可以为 null 的字符串,如果它不为 null,则长度必须在 2 到 255 个字符之间。
假设输入是 "Mighty is Awesome!"
,则 required&string&between:2,255|null
表达式对输入的结果将是 1&1&1|0
,这将产生 1
,即 true
,如果输入是 null
,则结果将是 0&0&0|1
= 1
,如果输入是 X
,则结果将是 0&0&0|0
= 0
等 ...
与其他验证实现不同,使用 位运算符 的 布尔代数 概念,可以构建出复杂且可读性高、紧凑的验证,同时将规则数量保持在最小,以反向或复合方式重用现有逻辑,并最终将代码库保持得尽可能简洁。以下是一些优势
- 规则可以被 NOT(使用
~
)执行与其正常行为相反的操作。 - 一个复杂的规则可以是两个或更多简单规则通过 AND(使用
&
)、OR(使用|
)或 XOR(使用^
)的组合的结果。 - 可以使用括号将规则组合在一起,并/或赋予它们更高的优先级,即 OPEN(使用
(
)和 CLOSE(使用)
)。 - 验证表达式还可以有行为,这是一个前缀验证表达式字符串的字符,它将影响所有规则。可用的行为包括
- NORMAL:执行所有规则,默认行为(无前缀)。
- OPTIMISTIC:在第一个成功后停止执行规则(使用
?
前缀)。 - PESSIMISTIC:在第一个失败后停止执行规则(使用
!
前缀)。
- 可以通过别名一些规则或将规则集作为宏添加,并使用
[macro]
语法执行它们来提高可读性。
此外,JSON 的概念确保了参数数据类型的安全性,而 CSV 的概念确保了参数列表有明确的解析规则。
最好的是,你不必记住所有规则或验证表达式语言的语法。Validation
类提供了一个流畅的接口,可以用来构建验证表达式。它了解所有 Mighty 可用的规则,并具有完整的 IDE-Intellisense 支持,使其变得非常简单。例如
use MAKS\Mighty\Validation; // the validation expression: `required&string&between:2,255|null` // can be constructed using the Validation::class as follows: $validation = (new Validation())->required()->string()->between(2, 255)->or()->null(); // AND is the default operator // or statically: $validation = Validation::required()->string()->between(2, 255)->or()->null();
事实: 描述验证表达式所做的事情通常比验证表达式本身需要更多的单词!
示例
以下是一些真实场景的示例
验证单个值
use MAKS\Mighty\Validator; $result = ($validator = new Validator()) ->validateOne( '123', $validator ->validation() // can be an integer or float or a string that is numeric // this example is only for demonstration only, // the same result can be achieved using numeric() only ->integer()->or()->float()->or()->group( fn ($validation) => $validation->string()->and()->numeric() ) ) ->toArray(); // $result would look something like this: [ 'value' => '123', 'result' => true, 'validations' => [ 'integer' => false, 'float' => false, 'string' => true, 'numeric' => true, ], 'errors' => [], 'metadata' => [ 'basis' => 'integer|float|(string&numeric)', 'rules' => 'integer|float|(string&numeric)', 'expression' => '0|0|(1&1)', ], ]; // you can also simply use the static helper Validator::validateData($data, $validation);
验证结构化数据
use MAKS\Mighty\Validator; use App\Service\HaveIBeenPwnedService as PasswordService; $validator = new Validator(); $data = [ 'name' => 'John Doe', 'age' => 32, 'email' => 'john.doe@domian.tld', 'username' => 'john.doe', 'password' => 'Secret@123', 'image' => '/path/to/image.png', 'submission' => 'now', 'consent' => 'yes', 'data' => [ 'nickname' => 'JOE', 'number' => 7, 'hobbies' => [ 'coding', 'cooking', 'reading', ] ], ]; $validations = [ 'name' => $validator->validation()->required()->string()->stringCharset(['UTF-8', 'ASCII'])->pessimistic(), // or using mVEL => required&string&string.charset:'["UTF-8","ASCII"]' 'age' => $validator->validation()->required()->integer()->min(18), // or using mVEL => required&integer&min:18 'email' => $validator->validation()->required()->email()->macro('gmail'), // or using mVEL => required&email&[gmail] 'username' => $validator->validation()->required()->username(), // or using mVEL => required&username 'password' => $validator->validation()->required()->password()->callback(fn ($input) => !PasswordService::isPwned($input)), // or using mVEL => required&password (NOTE: callback ist not possible, it requires a Validation::class instance that is bound to the Validator::class instance) 'image' => $validator->validation()->null()->xor()->group(fn () => $this->image()->imageDimensions(1920, 1080, '<=')), // or using mVEL => null^(image&image.dimensions:1920,1080,"<=") 'submission' => $validator->validation()->required()->datetime()->datetimeLt('2022-12-07'), // or using mVEL => required&datetime&datetime.lt:"2022-12-07" 'consent' => $validator->validation()->assert('${age.value}', 18, '>=')->or()->accepted()->or()->assertEquals('${this}', 'granted')->optimistic(), // or using mVEL => ?assert:${age.value},18,">="|accepted|assert.equals:${this},"granted" 'data' => $validator->validation()->required()->array()->arrayHasKey('nickname'), // or using mVEL => required&array&array.hasKey:"nickname" 'data.*' => $validator->validation()->scalar()->or()->array()->optimistic(), // or using mVEL => ?scalar|array 'data.nickname' => $validator->validation()->string()->min(2)->max(32), // or using mVEL => string&min:2&max:32 'data.hobbies.*' => $validator->validation()->ifEq('${data.hobbies.validations.array}', false)->or()->group(fn () => $this->string()->min(3)), // or using mVEL => if.eq:${data.hobbies.validations.array},false|(string&min:3) ]; $labels = [ 'name' => 'Name', 'age' => 'Age', 'email' => 'E-Mail', 'password' => 'Password', 'image' => 'Image', 'data' => 'Data', 'data.*' => 'Value of data', 'consent' => 'Consent', ]; $messages = [ '*' => [ // this will be expanded for all fields 'required' => '${@label} is a required field.', ], 'age' => [ 'min' => '${@label} must be at least ${@arguments.0}.', ], 'username' => [ 'matches' => '${@label} must contain letters, numbers, and the following characters ".-_" only.', ], 'consent' => [ 'assert' => 'You must be at least ${@arguments.1} years old to submit this form.', ] ]; $validator ->setData($data) ->setValidations($validations) ->setMessages($messages) ->setLabels($labels) ->validate(); $results = $validator->getResults(); // $result should look something like this: [ // this will actually be a Result object // array syntax is used here for demonstration purposes 'name' => [ 'key' => 'name', 'value' => 'John Doe', 'result' => true, 'validations' => [ 'required' => true, 'string' => true, 'string.charset' => true, ], 'errors' => [], 'metadata' => [ 'basis' => '!required&string&string.charset:["UTF-8","ASCII"]', 'rules' => 'required&string&string.charset:["UTF-8","ASCII"]', 'expression' => '1&1&1', ], ], // other validations ... ]; // you can also simply use the static helper Validator::validateData($data, $validations);
提示: 当向
Validator::class
提供消息覆盖时,建议使用 Rule\Validation::class
来设置数组键。此类包含所有 Mighty 内置规则名称作为类常量。
扩展验证器
验证器可以通过以下三种方式扩展
- 通过添加一个规则。
- 通过添加一个别名。
- 通过添加一个宏。
use MAKS\Mighty\Validator; use MAKS\Mighty\Rule; $validator = new Validator(); // adding a new rule $validator->addRule( (new Rule()) ->name('equals') ->arguments(['string']) ->callback(fn (string $input, mixed $expected): bool => $input == $expected) ->parameters(['@input', '@arguments.0']) ->comparison(['@output', '===', true]) ->example('equals:value') ->description('Asserts that the input is equal to the given value.') ); // adding a new rule alias $validator->addRuleAlias('eq', 'equals'); // adding a new rules macro $validator->addRuleMacro('gmail', 'string&email&matches:"/@gmail\.com$/i"'); $results = $validator->validateAll( [ 'name' => 'John', 'email' => 'john@doe.com', ], [ 'name' => 'eq:John', 'email' => 'required&[gmail]', ] ); // $results should look like this: [ // items will actually be a Result object // array syntax is used here for demonstration purposes 'name' => [ 'key' => 'name', 'value' => 'John', 'result' => true, 'validations' => [ 'eq' => true, ], 'errors' => [], 'metadata' => [ 'basis' => 'eq:John', 'rules' => 'eq:John', 'expression' => '1', ], ], 'email' => [ 'key' => 'email', 'value' => 'john@gmail.com', 'result' => false, 'validations' => [ 'required' => true, 'string' => true, 'email' => true, 'matches' => false, ],, 'errors' => [], 'metadata' => [ 'basis' => 'required&[gmail]', 'rules' => 'required&(string&email&matches:"/@gmail\.com$/i")', 'expression' => '1&(1&1&0)', ], ], ];
提示: 查看
rules
、aliases
和 macros
的默认值,以查看更多示例。
约束
Mighty 包含超过 250 个规则/属性,可用于验证任何数据或类、类常量、属性和方法值。
属性分为三大类
- 通用约束属性
- 特殊约束属性
- 规则约束属性
通用约束属性组
通用约束属性位于 MAKS\Mighty\Validation
命名空间下。
该组目前只包含一个属性,即 Constraint
属性。此属性接受一个验证表达式来验证其应用到的数据。它也是所有其他属性的基础类。
特殊约束属性组
特殊约束属性位于 MAKS\Mighty\Validation\Constraint
命名空间下。
该组包含仅在属性上下文中可用的特定任务的属性。它包括以下属性
Rule
:此属性用于使用单个验证规则验证任何数据。它也是规则约束属性组中所有属性的基础类。Callback
:此属性用于使用回调函数验证任何数据。Valid
:此属性用于验证可验证对象的合法性。Shape
:此属性用于验证数组或对象的形状。请注意,这是唯一一个用于验证一组值(结构化数据)而不是单个值的属性。Compound
:此属性用于将一组约束组合起来构建验证表达式。约束可以使用任何运算符进行组合,也可以具有行为。它提供了一种面向对象的方式来构建验证表达式。
注意: 允许与
Shape::class
和Compound::class
属性一起使用的约束必须是Constraint::class
、Rule::class
或Compound::class
的实际实例。不允许使用“特殊约束属性组”中的Callback::class
、Valid::class
或Shape::class
。如果您需要此功能,请提交问题,我们将讨论实现它。
规则约束属性组
规则约束属性位于MAKS\Mighty\Validation\Constraint\Rule
命名空间下。
该组包含基于单个验证规则的属性。它包括Mighty提供的大多数属性。有关完整列表,请参阅验证部分。
添加自定义约束
Mighty提供大量的内置约束,您很少需要Mighty之外的功能。不过,有时您可能需要自定义约束,下面是如何实现它的方法
<?php declare(strict_types=1); namespace App\Validation\Constraint; use Attribute; use MAKS\Mighty\Rule; use MAKS\Mighty\Result; use MAKS\Mighty\Validation\Strategy; use MAKS\Mighty\Validation\Constraint; use MAKS\Mighty\Validation\Constraint\ValidatesOne; // use the ValidatesMany interface if your Constraint returns a collection of Result objects use MAKS\Mighty\Validation\Constraint\ValidatesMany; #[Attribute( Attribute::TARGET_PROPERTY | Attribute::TARGET_METHOD )] class MyCustomConstraint extends Constraint implements ValidatesOne { public function __construct( ?string $message = null, Strategy $strategy = Strategy::FailFast, ) { parent::__construct( validation: 'app.myCustomConstraint', messages: ['app.myCustomConstraint' => $message], strategy: $strategy ); } public function validate(mixed $value = null): Result { // it is really up to you, how you handle this // you will just work with a normal Mighty Validator // here we're just preparing the data to pass to the Validator $name = ''; $data = [$name => $value]; $validations = [$name => $this->validation]; $messages = [$name => [$this->validation => $this->messages[$this->validation] ?? null]]; $labels = [$name => static::class]; // you can reuse the built-in rules or // add you own Rule that handles your custom logic $result = $this ->getValidator() ->addRule( // see MAKS\Mighty\Rule for more info (new Rule()) ->setName('app.myCustomConstraint') ->setCallback(static fn ($input) => $input /* here comes your logic */) ->setParameters(['@input']) // rule callback dependencies ->setMessage('${@label} must follow my custom constraint validation.') // this is the default message ) ->setData($data) ->setValidations($validations) ->setMessages($messages) ->setLabels($labels) ->validate(); return $result[$name]; // if you implement ValidatesMany, you will just return $result } }
注意: 自定义约束被视为“特殊约束属性组”的一部分(即不允许与/在
Shape::class
和Compound::class
约束一起使用)
验证
以下表格列出了所有可用的规则,包括它们的属性和方法等效项
规则和别名
宏
基准测试
到目前为止,您可能觉得Mighty做得太多,并且性能问题开始出现。但请放心,Mighty非常快,并且针对最佳性能进行了优化。以下是验证器性能的一些基准测试
非科学基准测试
Mighty验证器和Laravel验证器在Laravel应用程序中的性能。测试使用了一个包含50000个元素的数组,其中一半是整数,另一半是数字字符串。每个验证器被测试了10次(连续),收集了这10次测试的平均结果。
$data = array_merge(range(1, 25000), array_map('strval', range('25001', '50000'))); // Mighty Validator with XDebug disabled [ // required&integer 'preparationTime' => '1.32ms', // the time required to build the array 'validationTime' => '1107.29ms', // the time required to validate the array 'totalTime' => '1108.61ms', // the time required for the whole process ] // Mighty Validator with XDebug enabled [ // required&integer 'preparationTime' => '9.09ms', 'validationTime' => '6085.04ms', 'totalTime' => '6094.13ms', ] // Laravel Validator with XDebug disabled [ // required|integer 'preparationTime' => '1.33ms', 'validationTime' => '13882.72ms', 'totalTime' => '13884.05ms', ] // Laravel Validator with XDebug enabled [ // required|integer 'preparationTime' => '9.33ms', 'validationTime' => '24010.60ms', 'totalTime' => '24019.93ms', ]
因此,Mighty比禁用XDebug的Laravel验证器快约12.5倍,比启用XDebug的Laravel验证器快约4倍。
科学基准测试
基准测试使用PHPBench进行。以下是快速概述
PHPBench (1.2.6) running benchmarks...
with configuration file: mighty/phpbench.json.dist
with PHP version 8.1.9, xdebug ❌, opcache ❌
\MAKS\Mighty\Benchmarks\ConstraintBench
benchAValidValidatableObject............I4 ✔ Mo305.595074ops/s (±0.75%)
benchAnInvalidValidatableObject.........I4 ✔ Mo326.708522ops/s (±1.02%)
\MAKS\Mighty\Benchmarks\ValidatorBench
benchSingleValidationString.............I4 ✔ Mo0.02212ms (±1.59%)
benchSingleValidationObject.............I4 ✔ Mo0.126929ms (±1.63%)
benchBulkValidationObject...............I4 ✔ Mo9.345847ms (±0.62%)
benchBulkValidationString...............I4 ✔ Mo6.734188ms (±0.40%)
Subjects: 6, Assertions: 6, Failures: 0, Errors: 0
事实: 最新的基准测试结果也可以在CI管道中找到,它将在每次向上游推送/PR时更新。
注意
- Mighty默认生成非常友好的错误信息(目前仅限英文)。这些信息可以在Validator/Constraint基础上轻松覆盖。您可能希望用不同的语言显示这些信息,为此,可以使用
MAKS\Mighty\Rule::setMessageTranslator()
方法。这是一个设置全局消息翻译器的便捷方法,它接受一个闭包作为参数,该闭包获取包含占位符的原始消息,并必须返回该消息的翻译版本。 - 参考Mighty验证表达式语言规范以了解更多关于Mighty的理论知识,以及了解该语言的工作原理。
- 参考Mighty API文档以了解更多关于PHP API的信息。该API有良好的文档记录,应该不会遗漏任何内容。
- Mighty目前是一个纯验证库,它不对数据进行任何类型的转换。引擎和当前的设计足够灵活,可以轻松地在上面实现Sanitizer,目前没有计划实现这一点,但它可能是一个未来的里程碑。
许可证
Mighty是一个开源项目,许可协议为MIT。
版权所有(c)2022 Marwan Al-Soltany。保留所有权利。