lezhnev74 / pasvl
Array Validator (regular expressions for nested array, sort of)
Installs: 44 613
Dependents: 2
Suggesters: 0
Security: 0
Stars: 50
Watchers: 3
Forks: 7
Open Issues: 0
Requires
- ext-mbstring: *
Requires (Dev)
- phpunit/phpunit: ^7.0|^8.0|^9.0
- roave/security-advisories: dev-master
- squizlabs/php_codesniffer: ^3.5
README
PASVL - PHP Array Structure Validation Library
Think of a regular expression [ab]+
which matches a string abab
. Now imaging the same for arrays.
The purpose of this library is to validate an existing (nested) array against a template and report a mismatch. It has the object-oriented extendable architecture to write and add custom validators.
Note to current users: this version is not backwards compatible with the previous 0.5.6.
Installation
composer require lezhnev74/pasvl
Example
Refer to files in Example
folder.
Usage
Array Validation
// Define the pattern of the data, define keys and values separately $pattern = [ '*' => [ 'type' => 'book', 'title' => ':string :contains("book")', 'chapters' => [ ':string :len(2) {1,3}' => [ 'title' => ':string', ':exact("interesting") ?' => ':bool', ], ], ], ]; // Provide the data to match against the above pattern. $data = [ [ 'type' => 'book', 'title' => 'Geography book', 'chapters' => [ 'eu' => ['title' => 'Europe', 'interesting' => true], 'as' => ['title' => 'America', 'interesting' => false], ], ], [ 'type' => 'book', 'title' => 'Foreign languages book', 'chapters' => [ 'de' => ['title' => 'Deutsch'], ], ], ]; $builder = \PASVL\Validation\ValidatorBuilder::forArray($pattern); $validator = $builder->build(); try { $validator->validate($data); } catch (ArrayFailedValidation $e) { // If data cannot be matched against the pattern, then exception is thrown. // It is not always easy to detect why the data failed matching, the exception MAY sometimes give you extra hints. echo "failed: " . $e->getMessage() . "\n"; }
Optional String Validation
$pattern = ":string :regexp('#^[ab]+$#')"; $builder = \PASVL\Validation\ValidatorBuilder::forString($pattern); $validator = $builder->build(); $validator->validate("abab"); // the string is valid $validator->validate("abc"); // throws RuleFailed exception with the message: "string does not match regular expression ^[ab]+$"
Validation Language
This package supports a special dialect for validation specification. It looks like this:
Short language reference:
-
Rule Name Specify zero or one Rule Name to apply to the data. Optinal postfix
?
allows data to benull
. Refer to the set of built-in rules insrc/Validation/Rules/Library
. For custom rules read below underCustom Rules
. For example,:string?
describes strings andnull
. -
Sub-Rule Name Specify zero or more Sub-Rule Names to apply to the data AFTER the Rule is applied. Sub Rules are extra methods of the main Rule. For example,
:number :float
describes floats. -
Quantifier Specify quantity expectations for data keys. If none is set then default is assumed -
!
. Available quantifiers:!
- one key required (default)?
- optional key*
- any count of keys{2}
- strict keys count{2,4}
- range of keys count
For example:
$pattern = [":string *" => ":number"]; // the above pattern matches data: $data = ["june"=>10, "aug" => "11"];
Pattern Definitions
- as exact value
$pattern = ["name" => ":any"]; // here the key is the exact value $pattern = ["name?" => ":any"]; // here the key is the exact value, can be absent as well $pattern = [":exact('name')" => ":any"]; // this is the same
- as nullable rule
$pattern = ["name" => ":string?"]; // the value must be a string or null
- as rule with subrules
$pattern = ["name" => ":string :regexp('#\d*#')"]; // the value must be a string which contains only digits
- as rule with quantifiers
$pattern = [":string {2}" => ":any"]; // data must have exactly two string keys
Compound Definitions
This package supports combinations of rules, expressed in a natural language. Examples:
:string or :number
:string and :number
(:string and :number) or :array
There are two combination operators: and
, or
.
and
operator has precedence.
Both are left-associative.
Custom Rules
By default, the system uses only the built-in rules. However you can extend them with your own implementations. To add new custom rules, follow these steps:
- implement your new rule as a class and extend it from
\PASVL\Validation\Rules\Rule
- implement a new rule locator by extending a class
\PASVL\Validation\Rules\RuleLocator
- configure your validator like this:
$builder = ValidatorBuilder::forArray($pattern)->withLocator(new MyLocator()); // set your new locator $validator = $builder->build();
Built-in Rules
This package comes with a few built-in rules and their corresponding sub-rules (see in folder src/Validation/Rules/Library
):
:string
- the value must be string:regexp(<string>)
- provide a regular expression(the same as forpreg_match()
):url
:email
:uuid
:contains(<string>)
:starts(<string>)
:ends(<string>)
:in(<string>,<string>,...)
:len(<int>)
:max(<int>)
:min(<int>)
:between(<int>,<int>)
:number
:max(<int>)
:min(<int>)
:between(<int>, <int>)
:int
- the number must be an integer:float
- the number must be a float:positive
:negative
:in(<a>,<b>,<c>)
- the number must be within values (type coercion possible):inStrict(<a>,<b>,<c>)
- the number must be within values (type coercion disabled)
:exact(<value>)
:bool(<?value>)
- the value must be boolean, if optional argument is given the value must be exactly it:object
:instance(<fqcn>)
:propertyExists(<string>)
:methodExists(<string>)
:array
:count(<int>)
:keys(<string>,<string>,...)
:min(<int>)
- min count:max(<int>)
- max count:between(<int>, <int>)
- count must be within
:any
- a placeholder, any value will match
Hints
- PHP casts "1" to 1 for array keys:
$data = ["12" => ""]; $pattern_invalid = [":string" => ""]; $pattern_valid = [":number :int" => ""];
- Technically speaking PASVL is a non-deterministic backtracking parser, and thus it can't always show you what exact key did not match the pattern. That is because, say, a key can match different patterns and there is no way of knowing which one was meant to be correct. In such cases it returns a message like "no matches found at X level".
🏆 Contributors
- Greg Corrigan. Greg spotted a problem with nullable values reported as invalid.
- Henry Combrinck. Henry tested the library extensively on real data and found tricky bugs and edge cases. Awesome contribution to make the package valuable to the community.
- @Averor. Found a bug in parentheses parsing.
- Julien Gidel. Improved
regexp
sub-rule.
License
This project is licensed under the terms of the MIT license.