Leightweight standalone validation library

v4.1.0 2022-02-06 08:19 UTC

README

  __ _ _ __ ___  ___
 / _` | '__/ _ \/ __|
| (_| | | |  __/\__ \
 \__,_|_|  \___||___/

Ares is a lightweight standalone validation library.

Codacy Badge Latest Stable Version Total Downloads Minimum PHP Version Build Status Coverage Status

Table of Contents

Installation

Install the library via composer:

composer require bus-factor/ares

Basic Usage

<?php

use Ares\Ares;

// atomic types
$ares = new Ares(['type' => 'string']);
$valid = $ares->validate('John Doe');
$errors = $ares->getValidationErrors();

// complex/nested types
$ares = new Ares([
    'type' => 'map',
    'schema' => [
        'firstName' => ['type' => 'string', 'required' => true],
        'lastName' => ['type' => 'string', 'required' => true],
    ],
]);
$valid = $ares->validate(['firstName' => 'John', 'lastName' => 'Doe']);
$errors = $ares->getValidationErrors();

Validation Errors

The validate() method returns true if the provided data is valid, otherwise false.

The getValidationErrors() method returns an Ares\Validation\Error\ErrorCollection object that holds a list of Ares\Validation\Error\Error instances that are collected during the last data validation. The list of validation errors gets reset each time validate() is called.

Each Ares\Validation\Error\Error object implements the JsonSerializable interface and contains details about the error.

The Ares\Validation\Error\ErrorCollection object is iterable but also offers 2 convenience methods:

  • ::toArrayJsonApiStyle() - returns an array of arrays that reflects the json:api spec
  • ::toArrayNested - returns a nested array of error messages whose nested structure matches the input data's structure

Validation Options

Validation options may be passed on validation:

$schema = [];
$options = [];
$ares = new Ares($schema, $options);

Default validation options are:

Validator::OPTIONS_DEFAULTS = [
    'allBlankable'      => false,
    'allNullable'       => false,
    'allRequired'       => true,
    'allUnknownAllowed' => false,
]

allBlankable

This option applies to the type string only. If set true, blank values are considered valid. If set false, blank values are considered invalid.

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
    ],
];

$ares = new Ares($schema);

$ares->validate(['name' => ''], ['allBlankable' => true]); // -> true
$ares->validate(['name' => ''], ['allBlankable' => false]); // -> false

This option may be overridden per field by using the blankable rule:

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
        'email' => ['type' => 'string', 'blankable' => true],
    ],
];

$ares->validate(['name' => 'John Doe', 'email' => ''], ['allBlankable' => false]); // -> true

allNullable

If set true, null is considered a valid value. If set false, null is not considered a valid value.

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
    ],
];

$ares->validate(['name' => null], ['allNullable' => true]); // -> true
$ares->validate(['name' => null], ['allNullable' => false]); // -> false

This option may be overridden per field by using the nullable rule:

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
        'email' => ['type' => 'string', 'nullable' => true],
    ],
];

$ares->validate(['name' => 'John Doe', 'email' => null], ['allNullable' => false]); // -> true

allRequired

If set true (default) fields that are defined in the schema and not present in the input, are considered invalid. If set false fields that are defined in the schema and not present in the input, are considered valid.

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
    ],
];

$ares = new Ares($schema);
$ares->validate([], ['allRequired' => true]); // -> false
$ares->validate([], ['allRequired' => false]); // -> true

This option may be overridden per field by using the required rule:

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
        'email' => ['type' => 'string', 'required' => false],
    ],
];

$ares = new Ares($schema);
$ares->validate(['name' => 'John Doe'], ['allRequired' => true]); // -> true

allUnknownAllowed

This option applies to the type map only. If set true fields that occur in the input data but are not defined in the schema are considered invalid. If set false fields that occur in the input data but are not defined in the schema are considered valid.

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
    ],
];

$ares = new Ares($schema);
$ares->validate(['name' => 'John Doe', 'initials' => 'JD'], ['allUnknownAllowed' => false]); // -> false
$ares->validate(['name' => 'John Doe', 'initials' => 'JD'], ['allUnknownAllowed' => true]); // -> true

Validation Rules

allowed

The allowed validation rule checks if a value is in a given set of allowed values (enumeration).

Examples:

$ares = new Ares(['type' => 'string', 'allowed' => ['small', 'large']]);
$ares->validate('medium'); // -> false
$ares->validate('small'); // -> true

The allowed validation rule is the opposite of the forbidden validation rule.

blankable

The blankable rule applies to string typed values only. If set true, blank strings are considered valid. If set false, blank strings are considered invalid (default).

Examples:

$ares = new Ares(['type' => 'string', 'blankable' => false]);
$ares->validate(''); // -> false
$ares->validate('   '); // -> false
$ares->validate('John Doe'); // -> true

$ares = new Ares(['type' => 'string', 'blankable' => true]);
$ares->validate('   '); // -> true

The blankable validation rule may be used in combination with the allBlankable validation option.

datetime

The datetime validation rule applies to string typed values only. If set true, any parsable date/time string is considered valid. If set false, date/time validation will not take place at all. If set a specific date/time format string, the given value will be checked against that format too. See DateTime::createFromFormat() for details about format strings.

Examples:

$ares = new Ares(['type' => 'string', 'datetime' => true]);
$ares->validate('foo'); // -> false
$ares->validate('2018-03-23'); // -> true

$ares = new Ares(['type' => 'string', 'datetime' => 'd.m.Y H:i']);
$ares->validate('2018-03-23'); // -> false
$ares->validate('23.03.2019 00:20'); // -> true

directory

The directory validation rule checks if the given string value contains the path to an existing directory. If set true, only paths to existing directories are considered valid. If set false, all input is considered valid (no validation).

Examples:

$ares = new Ares(['type' => 'string', 'directory' => true]);
$ares->validate(''); // -> false
$ares->validate(__FILE__); // -> false
$ares->validate(__DIR__); // -> true

email

The email validation rule checks if a value is a valid email address. If set true, only valid email addresses are considered valid. If set false, all input is considered valid (no validation).

Examples:

$ares = new Ares(['type' => 'string', 'email' => true]);
$ares->validate('John Doe'); // -> false
$ares->validate('john.doe@example.com'); // -> true

file

The file validation rule checks if the given string value contains the path to an existing file. If set true, only paths to existing files are considered valid. If set false, all input is considered valid (no validation).

Examples:

$ares = new Ares(['type' => 'string', 'file' => true]);
$ares->validate(''); // -> false
$ares->validate(__DIR__); // -> false
$ares->validate(__FILE__); // -> true

forbidden

The forbidden validation rule checks if a value is in a given set of forbidden values (enumeration).

Examples:

$ares = new Ares(['type' => 'string', 'forbidden' => ['small', 'medium']]);
$ares->validate('medium'); // -> false
$ares->validate('large'); // -> true

The forbidden validation rule is the opposite of the allowed validation rule.

length

The length validation rule applies to string and list typed values. The length validation rule checks if a string, or list has a specified exact length.

Examples:

$ares = new Ares(['type' => 'string', 'length' => 3]);
$ares->validate('foobar'); // -> false
$ares->validate('foo'); // -> true

$ares = new Ares([
    'type' => 'list',
    'length' => 3,
    'schema' => [
        'type' => 'integer'
    ],
])
$ares->validate([1, 2]); // -> false
$ares->validate([1, 2, 3]); // -> true

max

The max validation rule applies to float and integer typed values only. The max validation rule checks if a value is equal to or smaller a specified maximum value.

Examples:

$ares = new Ares(['type' => 'integer', 'max' => 5]);
$ares->validate(6); // -> false
$ares->validate(2); // -> true

Note this validation rule will throw a Ares\Exception\InapplicableValidationRuleException when used in conjunction with non-supported value types.

maxlength

The maxlength validation rule applies to string and list typed values. The maxlength validation rule checks if a string, or list does not exceed a specified maximum length.

Examples:

$ares = new Ares(['type' => 'string', 'maxlength' => 5]);
$ares->validate('foobar'); // -> false
$ares->validate('foo'); // -> true

$ares = new Ares([
    'type' => 'list',
    'maxlength' => 3,
    'schema' => [
        'type' => 'integer'
    ],
])
$ares->validate([1, 2, 3, 4]); // -> false
$ares->validate([1, 2, 3]); // -> true

min

The min validation rule applies to float and integer typed values only. The min validation rule checks if a value is equal to or greater a specified minimum value.

Examples:

$ares = new Ares(['type' => 'integer', 'min' => 5]);
$ares->validate(4); // -> false
$ares->validate(8); // -> true

Note this validation rule will throw a Ares\Exception\InapplicableValidationRuleException when used in conjunction with non-supported value types.

minlength

The minlength validation rule applies to string and list typed values. The minlength validation rule checks if a string, or list is not shorter than a specified minimum length.

Examples:

$ares = new Ares(['type' => 'string', 'minlength' => 5]);
$ares->validate('foo'); // -> false
$ares->validate('foobar'); // -> true

$ares = new Ares([
    'type' => 'list',
    'minlength' => 3,
    'schema' => [
        'type' => 'integer'
    ],
])
$ares->validate([1, 2]); // -> false
$ares->validate([1, 2, 3]); // -> true

nullable

If set true, null is considered a valid value. If set false, null is considered an invalid value (default).

Examples:

$ares = new Ares(['type' => 'string', 'nullable' => false]);
$ares->validate(null); // -> false
$ares->validate('John Doe'); // -> true

$ares = new Ares(['type' => 'string', 'nullable' => true]);
$ares->validate(null); // -> true

The nullable validation rule may be used in combination with the allNullable validation option.

regex

The regex validation rule applies to string typed values only. The regex validation rule checks if a string matches a regular expression.

Examples:

$ares = new Ares([
    'type' => 'map',
    'schema' => [
        'key' => [
            'type' => 'string',
            'regex' => '/^[A-Z]{3}$/',
        ],
    ],
]);

$ares->validate(['key' => 'foobar']); // -> false
$ares->validate(['key' => 'FOO']); // -> true

required

Use the required rule to enforce the presence of a value. If set true, absent fields are considered invalid. If set false, absent fields are considered valid (default).

Examples:

$ares = new Ares([
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string', 'required' => true],
    ],
]);

$ares->validate([]); // -> false
$ares->validate(['name' => 'John Doe']); // -> true

The required validation rule may be used in combination with the allRequired validation option.

schema

The schema rule is mandatory when using type list, map, or tuple.

schema (list)

The validator expects the schema to define a list item's validation rules.

Examples:

$ares = new Ares([
    'type' => 'list',
    'schema' => [
        'type' => 'integer',
    ],
]);

$ares->validate(['foo', 'bar']); // -> false
$ares->validate([1, 2, 3]); // -> true

schema (map)

The validator expects the schema to define per field validation rules for associative array input.

Examples:

$ares = new Ares([
    'type' => 'map',
    'schema' => [
        'email' => ['type' => 'string', 'required' => true],
        'password' => ['type' => 'string', 'required' => true],
    ],
]);

$ares->validate(['email' => 'john.doe@example.com']); // -> false
$ares->validate(['email' => 'john.doe@example.com', 'password' => 'j4n3:)']); // -> true

schema (tuple)

The validator expects the schema to define validation rules per input array element. During validation input array elements are expected to be continuous indexed starting from 0 (0, 1, 2, ...).

Examples:

$ares = new Ares([
    'type' => 'tuple',
    'schema' => [
        ['type' => 'string', 'email' => true],
        ['type' => 'integer'],
    ],
]);

$ares->validate(['john.doe@example.com']); // -> false
$ares->validate([1 => 'john.doe@example.com', 2 => 23]); // -> false
$ares->validate(['john.doe@example.com', 23]); // -> true

Internally, all schema elements of a tuple are required and cannot be declared optional by schema.

type

The type rule is mandatory and defines the expected/allowed value type. Supported types are:

  • boolean
  • float
  • integer
  • numeric (float or integer)
  • string
  • map
  • list
  • tuple

Examples:

$ares = new Ares(['type' => 'float']);
$ares->validate(5); // -> false
$ares->validate('John Doe'); // -> false

Read the section Custom Types to find out how to define and reuse your own types.

unknownAllowed

The unknownAllowed validation rule checks if a map contains fields that are not defined in the schema. If set true, fields that are not defined in the schema are considered valid. If set false, fields that are not defined in the schema are considered invalid.

Examples:

$ares = new Ares([
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
    ],
    'unknownAllowed' => false,
]);

$ares->validate(['name' => 'John Doe', 'email' => 'john.doe@example.com']); // -> false
$ares->validate(['name' => 'John Doe']); // -> true

url

The url validation rule checks if a value is a valid URL.

Examples:

$ares = new Ares(['type' => 'string', 'url' => true]);
$ares->validate('example'); // -> false
$ares->validate('https://example.com'); // -> true

uuid

The uuid validation rule checks if a value is a valid UUID.

Examples:

$ares = new Ares(['type' => 'string', 'uuid' => true]);
$ares->validate('example'); // -> false
$ares->validate('609de7b6-0ef5-11ea-8d71-362b9e155667'); // -> true

Custom Types

Basically, a custom type is a user defined schema that is stored in and retrieved from a registry. Here's an example how it works:

use Ares\Ares;
use Ares\Schema\TypeRegistry;

TypeRegistry::register('GermanDateString', [
    'type' => 'string',
    ['datetime' => 'd.m.Y', 'message' => 'Invalid date format, try something like "24.02.2019"'],
]);

TypeRegistry::register('ListOfHobbies', [
    'type' => 'list',
    'schema' => [
        'type' => 'string',
        'allowed' => ['Reading', 'Biking'],
    ],
]);

TypeRegistry::register('Student', [
    'type' => 'map',
    'schema' => [
        'birthDate' => ['type' => 'GermanDateString'],
        'hobbies' => ['type' => 'ListOfHobbies', 'minlength' => 1],
    ],
]);

$schema = ['type' => 'Student'];

$ares = new Ares($schema);

$ares->validate(['birthDate' => '1998-06-14', 'hobbies' => []]); // false
$ares->validate(['birthDate' => '14.06.1998', 'hobbies' => ['Reading']]); // true

Previously registered types are unregistered using TypeRegistry::unregister(). All priviously registered types are unregistered at once using TypeRegistry::unregisterAll(). It is also possible to define recursive types.

Custom Validation Error Messages

Change the Validation Error Message of a single Rule

The following example shows how validation error messages can be customized:

// validation rule without custom message (default)
$ares = new Ares([
    'type' => 'integer',
]);

// validation rule with custom message
$ares = new Ares([
    ['type' => 'integer', 'message' => 'Pleaser provide an integer value']
]);

Just wrap your rule (key-value) into an array and add a 'message' key.

Localization of Validation Error Messages

All built-in validation rules use the Ares\Error\ErrorMessageRendererInterface to render the messages. If not specified, an instance of Ares\Error\ErrorMessageRenderer is created and passed to the validation process. If necessary, a custom error message renderer can be passed to the validator:

use Ares\Ares;
use Ares\Validation\Error\ErrorMessageRendererInterface;

class MyErrorMessageRenderer implements ErrorMessageRendererInterface
{
    // ...
}

// ...

$ares = new Ares($schema);

$ares->getValidator()->setErrorMessageRenderer(new MyErrorMessageRenderer());

$valid = $ares->validate($data);

Custom Validation Rules

The following simple example shows how custom validation rules are implemented and integrated:

use Ares\Ares;
use Ares\Schema\Type;
use Ares\Validation\Context;
use Ares\Validation\RuleRegistry;
use Ares\Validation\Rule\AbstractRule;

class ZipCodeRule extends AbstractRule
{
    public const ID = 'zipcode';
    public const ERROR_MESSAGE = 'Invalid ZIP code';

    /**
     * Returns all supported value types.
     *
     * @return array
     */
    public function getSupportedTypes(): array
    {
        return [
            Type::STRING,
        ];
    }

    /**
     * Perform the value validation.
     *
     * @param mixed   $args    Validation rule arguments.
     * @param mixed   $data    Data being validated.
     * @param Context $context Validation context.
     * @return bool
     */
    public function performValidation($args, $data, Context $context): bool
    {
        // implement validation ...

        // add error if the validation fails
        $context->addError(self::ID, self::ERROR_MESSAGE);

        // TRUE  - skip all following validation rules for the current field
        // FALSE - run all following validation rules for the current field
        return false; 
    }
}

RuleRegistry::register(ZipCodeRule::ID, new ZipCodeRule());

$schema = [
    'type' => 'string',
    'zipcode' => true,
];

$ares = new Ares($schema);

Sanitization

This following example shows how to sanitize data:

$schema = [
    'type' => 'map',
    'schema' => [
        'name' => ['type' => 'string'],
        'age' => ['type' => 'integer'],
        'active' => ['type' => 'boolean'],
    ],
];

$ares = new Ares($schema);

$data = [
    'name' => ' John Doe   ',
    'age' => '23',
    'active' => '1',
    'hobby' => 'Reading',
];

$sanitizedData = $ares->sanitize($data);

// Result:
// [
//     'name' => 'John Doe',
//     'age' => 23,
//     'active' => true,
// ]

As shown in the example, by default sanitization makes these adjustments:

  • Trim strings
  • Convert numeric strings into integer, or string values
  • Convert numeric non-empty strings into boolean values
  • Removes unknown fields from the input data

Sanitization Options

trimStrings

If set true (default) sorrounding whitespace will be removed from strings. If set false sorrounding whitespace will be preserved.

purgeUnknown

If set true (default) unknown fields (fields/indices not defined in the schema) will be removed from the input data. If set false unknown fields will be preserved.