Skip to content

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

'default:value', v::default($value), new NullPolicy\DefaultValue($value)

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

'optional', v::optional(), new NullPolicy\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:

$gate = (new Gate())
    ->array('tags', 'optional');  // absent → omitted, present must be an array

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 if required were not set.
  • Conditional Nullable: the field is no longer nullable — an explicitly-passed null or '' 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)