Skip to content

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

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

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