The goal of this library is to have a validator with no unexpected behavior like a string '1' passes an integer type rule. This is built for data integrity and not for performance/insanely fast validation.
TODO
- fix keys with dots or asterisks possible problem?
- rework rules to allow validating without process?
<?php
use DonnySim\Validation\Validator;
use DonnySim\Validation\RuleSet;
$validator = new Validator(
[
'agreement' => 'yes',
'email' => 'no-email',
'roles' => [
['id' => 1, 'name' => 'Developer'],
['id' => '2', 'name' => 'Admin'],
],
],
[
RuleSet::make('email')->required()->email(),
RuleSet::make('blocked')->setValueIfNotPresent(false)->booleanType(),
RuleSet::make('roles')->omit()->required()->arrayType(),
RuleSet::make('roles.*.id')->required()->integerType(),
RuleSet::make('agreement')->required()->accepted()->toBoolean(),
]
);
$data = $validator->getValidatedData();
// [
// 'agreement' => true,
// 'blocked' => false,
// 'roles' => [
// ['id' => 1],
// ],
// ];
The validator executes rules in the same order they were provided:
RuleSet::make('email')->email()->required(),
This will trigger email validation first and only then check the required field.
After first validation trigger the result will be cached, meaning that any changes to the validator like setData
will be ignored.
To reset the state call the reset
on the validator.
Each rule is a separate class, it must implement \DonnySim\Validation\Interfaces\RuleSetInterface
.
The rules get access to \DonnySim\Validation\Data\DataEntry
and \DonnySim\Validation\Process\ValidationProcess
to interact with the process.
If the rule fails, it must call the fail
method on the process with a failing \DonnySim\Validation\Message
.
Instead of providing all the required Message parameters one by one you can use the Message::forEntry
helper.
To stop the validating from proceeding call $process->stop()
. This will not mark the rule as failed, just prevent further processing
This is useful for changing the value or short-circuiting the validation.
To control whether the value should be extracted you can use $process->setShouldExtractValue(bool)
.
If rule contains state that needs to be cleaned up between runs,
implement the \DonnySim\Validation\Interfaces\CleanupStateInterface
interface.
This only runs after the validator finishes the whole validation process.
Because of flexibility, batching values for validation would increase the complexity of the validator
beyond the value that it would provide. For such cases you can introduce a cache variable in the rule where you
track necessary values while they are being validated. For example, you can check \DonnySim\Validation\Rules\Integrity\Distinct
.
In some cases that is not sufficient, for example some rules might be better having all values, like validating database entries,
for this we can combine the rule cache and retrieve all entries using $process->getAllEntries($entry->getPattern())
.
This will return an array of DataEntry
. Make sure you check the value before using it as the list will be of unvalidated entries.
Validator accepts \DonnySim\Validation\Interfaces\RuleSetGroupInterface
as rules:
use DonnySim\Validation\Validator;
use DonnySim\Validation\RuleSetGroup;
use DonnySim\Validation\RuleSet;
$clientGroup = RuleSetGroup::make([
RuleSet::make('client.name')->required(),
RuleSet::make('client.last_name')->required(),
]);
$validator = new Validator(
[
'foo' => null,
'client' => [
'last_name' => 'test',
],
],
[
RuleSet::make('foo')->required(),
$clientGroup,
]
);
This allows you to can create custom rule groups and combine them where necessary. Combined with Pipe to tweak the validation flow can lead to powerful and flexible rules groups.
The RuleSet is not macroable, that is you cannot add new rules dynamically. This is to encourage creating your own RuleSet instead of relying on magic accessors:
use DonnySim\Validation\Interfaces\RuleSetInterface;
use DonnySim\Validation\Rules\Traits\BaseRulesTrait;
use DonnySim\Validation\Rules\Traits\TypeRulesTrait;
class MyRules implements RuleSetInterface
{
use BaseRulesTrait;
use TypeRulesTrait;
// implement interface methods ... or use DonnySim\Validation\Rules\Traits\RuleSetBaseTrait
public function exists(string $table, string $column): static
{
return $this->rule(new DatabaseExists($table, $column));
}
}
By default, a DonnySim\Validation\Exceptions\ValidationException
is thrown on validation failure. You can override validation exceptions using:
use DonnySim\Validation\Validator;
Validator::setFailureHandler(static function (Validator $validator) {
throw new MyCustomValidationException($validator->resolveMessages());
});
Validator holds the raw messages from the failed rules that can be accessed via getMessages
,
but in most cases we want to get a better presentation of the messages to output to the
user - to resolve messages to custom format use resolveMessages
.
By default, it uses DonnySim\Validation\ArrayMessageResolver
to resolve messages when calling $validator->resolveMessages()
,
which returns messages as an array:
[
'path.attribute' => [
[
'key' => 'required', // failing rule name
'params' => [], // any params provided by failing rule
],
],
]
You can change the resolver at any given time by providing a custom implementation of DonnySim\Validation\Interfaces\MessageResolverInterface
to the
resolveMessages
, e.g.:
$validator->resolveMessages(new JsonMessageResolver());
You can also override the default message resolver:
use DonnySim\Validation\Validator;
Validator::setMessageResolverFactory(static function () {
return new MyCustomMessageResolver();
});
Now, any time the resolveMessages
is called MyCustomMessageResolver
will be used,
but you can still override it for individual cases by providing other resolver as the first argument.
To override validation messages and/or attribute names we can provide 3rd and 4th parameter of the validator:
use DonnySim\Validation\Validator;
use DonnySim\Validation\RuleSet;
$validator = new Validator(
['foo' => [null, null]],
[RuleSet::make('foo.*')->required()],
['foo.*.required' => ':attribute failed'], // messages
['foo.*' => 'foo *', 'foo.0' => 'foo 0'] // attribute name
);
By default, it uses DonnySim\Validation\MessageOverrideProvider
.
To override default override provider:
use DonnySim\Validation\Validator;
Validator::setOverrideProviderFactory(static function (array $messages, array $attributes) {
return new MyCustomOverrideProvider($messages, $attributes);
});
The field under validation must be "yes"
, "on"
, 1
, or true
.
This is useful for validating "Terms of Service" acceptance or similar fields.
The field under validation must be entirely alphabetic characters.
The field under validation may have alphanumeric characters, as well as dashes and underscores.
The field under validation must be entirely alphanumeric characters.
The field under validation must be of array type.
The field under validation must have a size between the given min and max. Strings, numerics and arrays are supported.
The field under validation must be able to be cast as boolean.
Accepted input are true
, false
, 'true'
, 'false'
, 1
, 0
, '1'
, '0'
, 'yes'
, 'no'
, 'on'
and 'off'
.
The field under validation must be of boolean type.
The field under validation must have a matching field of {field}_confirmation
.
For example, if the field under validation is password
, a matching password_confirmation
field must be present in the input.
The field under validation must be a valid, non-relative date according to the strtotime
PHP function.
The field under validation must be a value after a given date.
The dates will be passed into the strtotime
PHP function in order to be converted to a valid DateTime
instance.
Instead of passing a date string to be evaluated by strtotime
, you may specify another field to compare against the date:
use DonnySim\Validation\RuleSet;
RuleSet::make('ends')->dateAfter(RuleSet::ref('starts'));
The field under validation must be a value after or equal to the given date. For more information, see the Date After rule.
The field under validation must be a value preceding the given date.
The dates will be passed into the PHP strtotime
function in order to be converted into a valid DateTime instance.
In addition, like the Date After rule, the name of another field under validation may be supplied as the value of date
.
The field under validation must be a value preceding or equal to the given date.
The dates will be passed into the PHP strtotime
function in order to be converted into a valid DateTime
instance.
In addition, like the Date After rule, the name of another field under validation may be supplied as the value of date
.
The field under validation must be a value equal to the given date.
The dates will be passed into the PHP strtotime
function in order to be converted into a valid DateTime
instance.
The field under validation must match the given format.
This validation rule supports all formats supported by PHP's DateTime
class.
The field under validation must have a different value than field.
The field under validation must be numeric and must have an exact length of value.
When validating arrays, the field under validation must not have any duplicate values:
use DonnySim\Validation\RuleSet;
RuleSet::make('foo.*.id')->distinct();
The field under validation must be formatted as an email address.
This validation rule utilizes the egulias/email-validator
package for validating the email address.
By default, the Email::VALIDATE_RFC, Email::VALIDATE_DNS
validator is applied.
The field under validation must end with one of the given values.
The field under validation must be greater than the given field or value. Strings, numerics and arrays are supported.
The field under validation must be greater than or equal to the given field or value. Strings, numerics and arrays are supported.
The field under validation must be included in the given list of values, strict comparison.
The field under validation must be of integer type.
The field under validation must be an IPv4 or IPv6 address.
The field under validation must be an IPv4 address.
The field under validation must be an IPv6 address.
The field under validation must be less than the given field or value. Strings, numerics and arrays are supported.
The field under validation must be less than or equal to the given field or value. Strings, numerics and arrays are supported.
Alis to Greater Than Or Equal rule.
Alis to Less Than Or Equal rule.
The field under validation must not be included in the given list of values.
The field under validation may be null
.
The field under validation must be numeric.
The field under validation must be a float
or string representing float.
The field under validation must be a int
or string representing integer.
If field under validation is missing, skip validation flow.
The field data under validation will not be extracted to validated data. This is useful when a field, and it's children are validated but only children data should be included. For example:
use DonnySim\Validation\Validator;
use DonnySim\Validation\RuleSet;
$validator = new Validator(
[
'roles' => [
['id' => 1, 'name' => 'Developer'],
['id' => '2', 'name' => 'Admin'],
],
],
[
RuleSet::make('roles')->omit(),
RuleSet::make('roles.*.id')->required()->integerType(),
]
);
$data = $validator->getValidatedData();
//[
// 'roles' => [
// ['id' => 1],
// ],
//];
Pass through callback. This allows to inspect, e.g.:
use DonnySim\Validation\RuleSet;
RuleSet::make('roles.*')->arrayType()->pipe('dd');
Insert additional rules to be executed right after the pipe, e.g.:
use DonnySim\Validation\Data\DataEntry;
use DonnySim\Validation\RuleSet;
use DonnySim\Validation\Process\ValidationProcess;
RuleSet::make('roles.*')
->arrayType()
->pipe(function (DataEntry $entry, ValidationProcess $process) {
if (isset($entry->getValue()['temp_id'])) {
$process->getCurrent()->insert(RuleSet::make()->rule(CreateCustomRule::class));
} else {
$process->getCurrent()->insert(RuleSet::make()->rule(UpdateCustomRule::class));
}
})
// Will be called after CreateCustomRule or UpdateCustomRule
->rule(FinalRule::class);
or change the rules to be processed, e.g.:
use DonnySim\Validation\Data\DataEntry;
use DonnySim\Validation\RuleSet;
use DonnySim\Validation\Process\ValidationProcess;
RuleSet::make('roles.*')
->arrayType()
->pipe(function (DataEntry $entry, ValidationProcess $process) {
if (isset($entry->getValue()['temp_id'])) {
$process->getCurrent()->replace(RuleSet::make()->rule(CreateCustomRule::class));
} else {
$process->getCurrent()->replace(RuleSet::make()->rule(UpdateCustomRule::class));
}
})
// Will never be called, "replace" replaces further rules
->rule(FinalRule::class);
The field under validation must be present in the input data but can be empty.
The field under validation must be present in the input data and not empty. A field is considered "empty" if one of the following conditions are true:
- The value is
null
. - The value is an empty string, trimmed before checking.
- The value is an empty array.
Allows adding custom rule.
Allows adding multiple custom rules.
When the field under validation is missing, set a custom value. This will stop the validator from proceeding.
The field under validation must start with one of the given values.
The field under validation must be of string type.
Cast the value to boolean. It will be converted to true
if it matches 'true'
, 1
, '1'
, 'yes'
, 'on'
, otherwise it will be false
.
Cast the value to integer. It will be cast using PHP cast - (int)$value
.
Cast the value to string. The following logic will be used:
- return
'true'
or'false'
if the value is bool; - return
'array'
if the value is array; - return
(string)$value
otherwise;