Skip to content

lezhnev74/pasvl

master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
 
 
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Latest Stable Version Build Status Total Downloads License

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 be null. Refer to the set of built-in rules in src/Validation/Rules/Library. For custom rules read below under Custom Rules. For example, :string? describes strings and null.

  • 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 for preg_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.