0

Conditional validation — sometimes, required_if, required_with

Intermediate5 min read·lv-11-004

Concept

Not every field should always be validated — sometimes a field is optional and should only be validated if present, or its requirement depends on the value of another field. Laravel provides a set of conditional validation rules for exactly this purpose: sometimes, required_if, required_unless, required_with, required_with_all, required_without, and required_without_all.

The sometimes modifier tells the Validator to only run the subsequent rules for a field if that field is present in the input data. This is the correct tool for optional API fields that, when provided, must conform to specific constraints. Without sometimes, writing 'phone' => 'string|max:20' would pass even if phone is missing — which is fine — but sometimes makes the intent explicit and is required when combined with rules that would otherwise fail on absence.

required_if:other_field,value makes a field required when another field equals a specific value. For example, a shipping address is required if the user selects physical delivery. The inverse is required_unless:other_field,value. Both support comma-separated value lists.

required_with:foo,bar makes a field required if any of the listed fields are present and non-empty. required_with_all:foo,bar requires all listed fields to be present. Their inverses are required_without and required_without_all.

For complex conditional logic, the Validator::sometimes() method accepts a closure that receives the validated data and can add rules dynamically. This is the escape hatch for conditions that cannot be expressed as a simple rule string — for example, requiring a VAT number only when the country is an EU member state:

php
$validator->sometimes('vat_number', 'required|regex:/^[A-Z]{2}\d+$/', function ($input) {
    return in_array($input->country, EU_COUNTRIES);
});

The closure receives the current input as a Fluent instance (not an array), so property access syntax works: $input->country. Multiple sometimes() calls can be chained, and each closure is evaluated independently after the initial rule pass completes.

RuleBehaviour
sometimesRun rules only if the field is present in input
required_if:field,valueRequired when field equals value
required_unless:field,valueRequired unless field equals value
required_with:a,bRequired if any of a, b are present and non-empty
required_with_all:a,bRequired if all of a, b are present and non-empty
required_without:a,bRequired if any of a, b are missing or empty
required_without_all:a,bRequired if all of a, b are missing or empty

Code Example

php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

$validator = Validator::make($request->all(), [
    // Only validate 'phone' if it's present in the payload
    'phone' => ['sometimes', 'string', 'min:7', 'max:20'],

    // delivery_method is required
    'delivery_method' => ['required', 'in:digital,physical'],

    // shipping_address required ONLY when delivery_method is 'physical'
    'shipping_address' => ['required_if:delivery_method,physical', 'string', 'max:500'],

    // coupon_code required UNLESS order_total is above 100
    'coupon_code' => ['required_unless:order_total,100', 'string', 'max:30'],

    // billing_name required if either billing_street or billing_city is present
    'billing_name'   => ['required_with:billing_street,billing_city', 'string', 'max:255'],
    'billing_street' => ['sometimes', 'string', 'max:255'],
    'billing_city'   => ['sometimes', 'string', 'max:100'],
]);

// Complex condition: VAT number required for EU business accounts
$validator->sometimes('vat_number', ['required', 'regex:/^[A-Z]{2}[0-9A-Z]+$/'], function ($input) {
    return $input->account_type === 'business'
        && in_array($input->country_code, ['DE', 'FR', 'IT', 'ES', 'NL', 'BE', 'PL', 'SE']);
});

// Multiple conditional rules for different fields
$validator->sometimes('company_name', ['required', 'string', 'max:255'], function ($input) {
    return $input->account_type === 'business';
});

$validator->sometimes('date_of_birth', ['required', 'date', 'before:-18 years'], function ($input) {
    return $input->account_type === 'individual';
});

if ($validator->fails()) {
    throw new \Illuminate\Validation\ValidationException($validator);
}

Interview Q&A

Q: What is the difference between sometimes and nullable in Laravel validation?

nullable declares that the field may have a null value — it still needs to be present in the input, and if present and non-null it must pass all subsequent rules. sometimes declares that the entire rule list for a field should be skipped if the field is absent from the input altogether. A common pattern is combining both: ['sometimes', 'nullable', 'url'] means "if provided, it may be null; if non-null, it must be a URL." Without sometimes, a missing field still has its rules evaluated, which matters for rules that care about key presence like required_with.


Q: How does Validator::sometimes() differ from putting required_if in the rules array?

The string-based required_if:field,value rules are limited to simple equality checks against a single other field value. Validator::sometimes() accepts a PHP closure that receives the full current input as a Fluent object, enabling arbitrary conditional logic — checking membership in a set, combining multiple field values, calling external functions, or evaluating computed conditions. It also lets you conditionally apply entire rule sets (multiple rules at once) rather than individual rules. The closure is evaluated after the initial validation pass, so you can trust that the condition fields have already been sanitised by earlier rules.


Q: When would you use required_without versus required_with_all, and give a real-world example?

required_without:a,b is useful when you need at least one of several alternative contact methods — for instance, requiring email to be provided only if neither phone nor sms_number was given. required_with_all is useful for composite requirements — for instance, if both latitude and longitude are provided, a location_label becomes mandatory. A real-world pattern: shipping address form where shipping_street becomes required when shipping_city AND shipping_country are both present (required_with_all:shipping_city,shipping_country), because a partial address is worse than no address.