Conditional validation
Rules contain several options for skipping themselves under certain conditions. Not every rule supports all of these options, but the vast majority do.
skipOnError
- skip a rule in the set if the previous one failed
By default, if an error occurs while validating an attribute, all further rules in the set are processed. To change this behavior, use skipOnError: true
for rules that need to be skipped:
In the following example, checking the length of a username is skipped if the username is not filled.
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Number;
use Yiisoft\Validator\Rule\Regex;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'name' => [
// Validated.
new Required(),
// Skipped because "name" is required but not filled.
new Length(min: 4, max: 20, skipOnError: true),
// Validated because "skipOnError" is "false" by default. Set to "true" to skip it as well.
new Regex('^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'),
],
'age' => [
// Validated because "age" is a different attribute with its own set of rules.
new Required(),
// Validated because "skipOnError" is "false" by default. Set to "true" to skip it as well.
new Number(min: 21),
],
];
$result = (new Validator())->validate($data, $rules);
Note that this setting must be set for each rule that needs to be skipped on error.
The same effect can be achieved with StopOnError
and Composite
rules, which can be more convenient for a larger number of rules.
Using StopOnError
:
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Regex;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Rule\StopOnError;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'name' => new StopOnError([
new Required(),
new Length(min: 4, max: 20),
new Regex('^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'),
]),
];
$result = (new Validator())->validate($data, $rules);
Using Composite
:
use Yiisoft\Validator\Rule\Composite;
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Regex;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'name' => [
new Required(),
new Composite(
[
new Length(min: 4, max: 20),
new Regex('^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'),
],
skipOnError: true,
)
],
];
$result = (new Validator())->validate($data, $rules);
skipOnEmpty
- skipping a rule if the validated value is "empty"
By default, missing/empty values of attributes are validated. If the value is missing, it is assumed to be null
. If you want the attribute to be optional, use skipOnEmpty: true
.
An example with an optional language attribute:
use Yiisoft\Validator\Rule\In;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'language' => [
new In(['ru', 'en'], skipOnEmpty: true),
],
];
$result = (new Validator())->validate($data, $rules);
If the attribute is required, it is more appropriate to use skipOnError: true
along with the preceding Required
rule instead of skipOnEmpty: true
. This is because the detection of empty values within the Required
rule and skipping in further rules can be set separately. This is described in more detail below, see Configuring empty condition in other rules section.
use Yiisoft\Validator\Rule\In;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'language' => [
new Required(),
new In(['ru', 'en'], skipOnError: true),
],
];
$result = (new Validator())->validate($data, $rules);
Empty condition basics
What is considered empty can vary depending on the scope of usage.
The value passed to skipOnEmpty
is called "empty condition". Due to normalization the following shortcut values are supported:
- When
false
ornull
,Yiisoft\Validator\EmptyCondition\NeverEmpty
is used automatically as a callback - every value is considered non-empty and validated without skipping (default). - When
true
,Yiisoft\Validator\EmptyCondition\WhenEmpty
is used automatically as a callback - only passed (corresponding attribute must be present) and non-empty values (notnull
,[]
, or''
) are validated. - If a custom callback is set, it is used to determine emptiness.
false
is usually more suitable for HTML forms and true
- for APIs.
There are some more conditions that have no shortcuts and need to be set explicitly because they are less used:
Yiisoft\Validator\EmptyCondition\WhenMissing
- a value is treated as empty only when it is missing (not passed at all).Yiisoft\Validator\EmptyCondition\WhenNull
- limits empty values tonull
only.
An example with using WhenNull
as parameter (note that an instance is passed, not a class name):
use Yiisoft\Validator\Rule\Integer;
use Yiisoft\Validator\EmptyCondition\WhenNull;
new Integer(max: 100, skipOnEmpty: new WhenNull());
Custom empty condition
For even more customization you can use your own class that implements the __invoke()
magic method. Here is an example where a value is empty only if it is missing (when using attributes) or equals exactly to zero.
use Yiisoft\Validator\Rule\Number;
final class WhenZero
{
public function __invoke(mixed $value, bool $isAttributeMissing): bool
{
return $isAttributeMissing || $value === 0;
}
}
new Integer(max: 100, skipOnEmpty: new WhenZero());
or just a callable:
use Yiisoft\Validator\Rule\Integer;
new Integer(
max: 100,
skipOnEmpty: static function (mixed $value, bool $isAttributeMissing): bool {
return $isAttributeMissing || $value === 0;
}
);
Using the class has the benefit of the code reusability.
Using the same non-default empty condition for all the rules
For multiple rules, this can also be more conveniently set at the validator level:
use Yiisoft\Validator\RuleHandlerResolver\SimpleRuleHandlerContainer;
use Yiisoft\Validator\Validator;
$validator = new Validator(skipOnEmpty: true); // Using the shortcut.
$validator = new Validator(
new SimpleRuleHandlerContainer(),
// Using the custom callback.
skipOnEmpty: static function (mixed $value, bool $isAttributeMissing): bool {
return $value === 0;
}
);
Configuring empty condition in other rules
Some rules, such as Required
can't be skipped for empty values - that would defeat the purpose of the rule. However, empty condition can be configured here for detecting when a value is empty. Note - this does not skip the rule. It only determines what the empty condition is:
use Yiisoft\Validator\Rule\Required;
$rule = new Required(
emptyCondition: static function (mixed $value, bool $isAttributeMissing): bool {
return $isAttributeMissing || $value === '';
},
);
It is also possible to set it globally for all rules of this type at the handler level via RequiredHandler::$defaultEmptyCondition
.
when
when
provides the option to apply the rule depending on a condition of the provided callable. A callable's result determines if the rule will be skipped. The signature of the function is the following:
function (mixed $value, ValidationContext $context): bool;
where:
$value
is a validated value;$context
is a validation context;true
as a returned value means that the rule must be applied and afalse
means it must be skipped.
In this example the state is only required when the country is Brazil
. $context->getDataSet()->getAttributeValue()
method allows you to get any other attribute's value within the ValidationContext
.
use Yiisoft\Validator\Rule\Length;
use Yiisoft\Validator\Rule\Required;
use Yiisoft\Validator\ValidationContext;
use Yiisoft\Validator\Validator;
$data = [];
$rules = [
'country' => [
new Required(),
new Length(min: 2),
],
'state' => new Required(
when: static function (mixed $value, ValidationContext $context): bool {
return $context->getDataSet()->getAttributeValue('country') === 'Brazil';
},
)
];
$result = (new Validator())->validate($data, $rules);
As an alternative to functions, callable classes can be used instead. This approach has the advantage of code reusability. See the Skip on empty section for an example.
Back to Yii Validator