Null policy reference (Stage 2)¶
The null policy is the second stage of the pipeline. It runs only when a value is null
or '' — either because the input was absent/empty, or because a converter returned a
null signal.
A null policy returns one of three sealed signals:
| Signal | Meaning |
|---|---|
shortCircuit($value) |
Put $value in data and stop the pipeline (success) |
skip() |
Omit this key from data entirely (silent success) |
reject($error) |
Validation failure |
Exactly one null policy per property. Required and Nullable are mutually exclusive —
adding one replaces the other (last wins).
Use use Verja\Verja as v or use Verja\NullPolicy to access null policies.
Default behaviour (no policy)¶
When no null policy is set, absent/empty values are silently omitted from data. This
is the default for properties defined with ->any(), ->string(), ->int(), etc.
Properties defined with ->array() and ->object() get a Required policy by default.
Pass 'nullable' or v::nullable() to override.
Required¶
'required', 'required:condition', v::required($condition = null),
new NullPolicy\Required($condition = null)
The value must be present and non-empty (null and '' both fail).
Error key: IS_EMPTY.
$gate = (new Gate())
->string('name', 'required') // must be present and non-empty
->string('email', 'required', 'emailAddress')
->string('bio'); // optional — omitted if absent or empty
Nullable¶
'nullable', 'nullable:condition', v::nullable($condition = null),
new NullPolicy\Nullable($condition = null)
Allows the value to be null or absent. Empty strings ('') are normalised to null.
The field appears as null in data — no further filters or validators run.
$gate = (new Gate())
->string('nickname', 'nullable') // absent or '' → null in data
->string('bio', 'nullable', 'strLen:0:500') // non-null values still validated
->int('age', 'nullable', 'between:0:150');
DefaultValue¶
Substitutes a default when the value is null/empty. The default is placed in data and
the pipeline stops (no further filters or validators run).
$value may be a static value or a callable that receives the sibling context array:
v::default(0)
v::default('guest')
v::default(fn(array $ctx) => $ctx['type'] === 'divider' ? null : 'untitled')
$gate = (new Gate())
->string('role', v::default('user'), 'inArray:["user","admin"]')
->string('message', v::default(fn($ctx) => $ctx['type'] === 'divider' ? null : ''));
Optional¶
Absent/empty values are silently omitted from data. Functionally identical to having no
policy, but explicit. Useful to make an ->array() / ->object() property optional
without making it nullable:
Conditional Required / Nullable¶
Both Required and Nullable accept an optional condition evaluated against the
sibling context (the other fields at the same object level). The policy 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 NullPolicy\Required(fn(array $ctx) => in_array($ctx['role'], ['admin', 'moderator']))
new NullPolicy\Nullable(fn(array $ctx) => $ctx['type'] === 'divider')
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 omitted, exactly as ifrequiredwere not set. - Conditional
Nullable: the field is no longer nullable — an explicitly-passednullor''fails validation.
$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, omitted
Required with fallback¶
Required accepts an optional $fallback applied when the condition is not met. The
fallback defaults to Optional (omit when absent), but can be changed:
// required when type != 'draft', nullable otherwise
new NullPolicy\Required('type != "draft"', 'nullable')
// required when active=1, default 'n/a' otherwise
new NullPolicy\Required('active', 'default', 'n/a')
// string shorthands for $fallback:
// 'nullable' → new NullPolicy\Nullable()
// 'optional' / 'omit' → new NullPolicy\Optional()
// 'default' → new NullPolicy\DefaultValue($default)