Required and nullable¶
Properties defined with the scalar typed methods (->string(), ->int(), ->boolean(),
->number(), ->date(), ->any()) are optional by default — absent or empty values
are silently skipped.
Properties defined with ->array() and ->object() are required by default. Pass
'nullable' to make them optional.
Mark any property as required or nullable by passing the corresponding prefix validator in its definition.
Required and Nullable are mutually exclusive: adding one removes the other.
Required¶
The value must be present and non-empty (null and '' fail).
Error key: IS_EMPTY.
$gate = (new Gate())
->string('name', 'required') // must be present and non-empty
->string('email', 'required', 'emailAddress')
->string('bio'); // optional — skipped if absent or empty
Nullable¶
Allows the value to be null or absent. Empty strings ('') are cast to null before
any further validators run. Once cast to null, the pipeline short-circuits — no subsequent
validators run. Error key: none (silent).
$gate = (new Gate())
->string('nickname', 'nullable') // absent or '' → null, skipped
->string('bio', 'nullable', 'strLen:0:500') // non-null values still validated
->int('age', 'nullable', 'between:0:150');
Conditional required / nullable¶
Both validators accept an optional condition — a string expression or a callable — that is evaluated against the sibling context (the other fields at the same object level). The validator only takes effect when the condition is true.
// required only when 'newsletter' is truthy
->string('email', 'required:newsletter')
// nullable only when 'type' equals 'divider'
->string('label', 'nullable:type = "divider"')
// callable — receives sibling context as array
->string('label', v::nullable(fn(array $ctx) => $ctx['type'] === 'divider'))
->string('tax_code', v::required(fn(array $ctx) => $ctx['country'] === 'IT'))
Condition syntax¶
String conditions follow this grammar:
| Form | True when… |
|---|---|
'key' |
$context['key'] is truthy |
'!key' |
$context['key'] is falsy |
'key = value' |
left equals right |
'key != value' |
left does not equal right |
'key > value' |
left is greater than right |
'key >= value' |
left is greater than or equal to right |
'key < value' |
left is less than right |
'key <= value' |
left is less than or equal to right |
Right-hand side values:
| Literal | Example |
|---|---|
| Boolean | true, false |
| Number | 42, 3.14 |
| Quoted string | "divider" |
| Key reference | other_field (looked up in context) |
'required:active' // active is truthy
'required:!draft' // draft is falsy
'required:role = "admin"' // role equals the string "admin"
'nullable:type != "required-type"' // type is not "required-type"
'required:score >= 100' // score is at least 100
'nullable:min_age = max_age' // min_age and max_age are equal (key ref)
Callable conditions¶
A callable receives the full sibling context as an array and must return bool:
new Nullable(fn(array $ctx) => $ctx['type'] === 'divider')
new Required(fn(array $ctx) => in_array($ctx['role'], ['admin', 'moderator']))
Context scope
The context is always the current object level. For a top-level Gate that is the
full input; for a nested Gate inside ->object() it is the fields of that nested object.
Behaviour when the condition is not met¶
- Conditional
Required: the field behaves as optional — absent or empty values are silently skipped, exactly as ifrequiredwere not set. - Conditional
Nullable: the field is no longer nullable — an explicitly-passednullor''is not accepted and will fail the next validator.
$gate = (new Gate())
->boolean('newsletter')
->string('email', 'required:newsletter');
$gate->isValid(['newsletter' => true, 'email' => '']) // false — required, empty
$gate->isValid(['newsletter' => true, 'email' => 'a@b.c']) // true
$gate->isValid(['newsletter' => false, 'email' => '']) // true — condition not met
$gate->isValid(['newsletter' => false]) // true — absent, skipped