skip to content

Rules & Rule Objects

Laravel's rule catalogue, from pipe strings and bail to Rule::unique()->ignore(), enum, array.* and Password::defaults(), plus custom rule objects. Interviewers probe nullable versus sometimes.

on this pageshow

explore

questions

6

In Laravel validation, what is the difference between the nullable, sometimes and present rules, and when do you need each?

level: middleimportance: must knowfreq 65%

answer

  1. absent key vs key sent as null
  2. ConvertEmptyStringsToNull turns '' into null
  3. nullable: null skips the other rules
  4. sometimes: validate only if the key exists
  5. present: key must exist, may be empty

basics

~20 s

nullable lets a present field be null and then skips its other rules; sometimes runs a field's rules only when the key exists in the input; present requires the key to exist but accepts an empty value.

solid answer

~40 s

Laravel's validator skips ordinary rules for a key that is absent or an empty string, but a key sent as `null` is present, so `string` or `date` run and fail. Because the default middleware stack includes `TrimStrings` and `ConvertEmptyStringsToNull`, an empty form field arrives as `null` — hence `nullable` on optional fields. `sometimes` works on presence: if the key is missing, none of the field's rules run, including `required`, so `['sometimes', 'required', 'string']` means "optional, but not blank when sent" — typical for a PATCH. `present` is an implicit rule that fails when the key is missing yet accepts `null` or an empty value, useful when the client must send the field even if it is empty.

code

php · 12 lines
php
<?php

use Illuminate\Validation\Rule;

$rules = [
    'username' => [
        'sometimes', 'required', 'string', 'max:30',
        Rule::unique('users', 'username')->ignore($user),
    ],
    'bio' => ['sometimes', 'nullable', 'string', 'max:300'],
    'interests' => ['present', 'array'],
];

go deeper

for a junior

Remember to add nullable to optional form fields, because empty inputs arrive as null.

for a middle

Explain absent versus null, implicit versus ordinary rules, and why sometimes|required suits PATCH endpoints.

for a senior

Design API contracts where 'not sent' and 'sent as null' mean different things, and choose present, filled or missing accordingly.

for a principal

Standardise how partial updates and field clearing are expressed across the API so clients never guess.

## Two different questions: is the key there, and is it null? Laravel's validator distinguishes three states for a field: 1. **Absent** — the key is not in the input at all. 2. **Present but empty** — the key exists with `null`, an empty string, or an empty array. 3. **Present with a value**. Rules come in two kinds: - **Implicit rules** (`required`, `required_if`, `present`, `filled`, `accepted`, `missing`...) always run, even for absent or empty fields. - **Ordinary rules** (`string`, `email`, `max`, `date`...) are **skipped** when the key is absent or holds an empty string, but they **do run** when the key holds `null`. That last point is the source of most confusion. ## Why null shows up so often A new Laravel app's global middleware stack includes `TrimStrings` and `ConvertEmptyStringsToNull`. An HTML input left blank is posted as `""`, trimmed, and converted to `null` before validation. So an optional "bio" field that the user leaves empty reaches the validator as **present and null**, and `['string', 'max:300']` fails with "The bio field must be a string." ## nullable `nullable` says "null is acceptable". When the value is `null`, the validator skips the field's ordinary rules. Implicit rules still run, so `['nullable', 'required']` still fails on null. - Use on every optional field of a browser form. - In `validated()`, a nullable field sent as null appears with the value `null`. ## sometimes `sometimes` says "only validate this field if the key is present". If the key is absent, **every** rule for that field is skipped — including implicit ones like `required`. - `['sometimes', 'required', 'string', 'max:30']` → may be omitted; if sent, must not be empty. - Typical for PATCH endpoints where clients send only the fields they change. - Without `sometimes`, `required` on a PATCH forces the client to resend everything. (`Validator::sometimes()`, the method that adds rules under a closure condition, is a different feature with the same word.) ## present and filled - **`present`** — an implicit rule that passes whenever the key exists, whatever the value. Use it when the contract says "always send this key, even as null or an empty list". - **`filled`** — if the key exists, it must not be empty; if absent, it passes. Close to `sometimes|required`. - **`missing`** — the opposite of `present`: the key must not be sent. ## Behaviour table Rules on `bio` and what happens for each input: | Rules | key absent | `null` | `"hello"` | |---|---|---|---| | `string` | passes (skipped) | **fails** | passes | | `nullable, string` | passes | passes | passes | | `required, string` | fails | fails | passes | | `sometimes, required, string` | passes (skipped) | fails | passes | | `present` | fails | passes | passes | | `filled` | passes | fails | passes | ## Choosing for the profile-edit scenario - `username` on a full-form PUT: `['required', 'string', 'max:30', ...]`. - `username` on a PATCH: `['sometimes', 'required', 'string', 'max:30', ...]` — optional, never blank. - `bio`: `['nullable', 'string', 'max:300']` — may be cleared. - `interests`: `['present', 'array']` if the client must always send the list, even empty, so "not sent" is not mistaken for "cleared". ## Traps - Calling the validator directly on an array (not an HTTP request) skips the middleware, so an empty string stays `""` and ordinary rules are skipped for it — behaviour differs from the same rules on a form post. - `nullable` does not make a field optional in the sense of "may be absent" — an absent key was already fine for ordinary rules; `nullable` is about the explicit null. - `sometimes` placed on a field that the browser always sends (every text input is sent, even empty) has no effect; the key is always present. - Unchecked checkboxes are **not** sent at all, so on a checkbox field the key really is absent — the one browser case where `sometimes`, `present` and `required` behave very differently. ## How to answer in an interview Lead with the two axes — presence of the key, and null-ness of the value — then place each rule on them: `sometimes` acts on presence, `nullable` on null, `present` demands the key. Mentioning `ConvertEmptyStringsToNull` shows you know why `nullable` appears on nearly every optional field in real Laravel code.

  • Why does ['string'] pass when the key is missing but fail when it is sent as null?
    The validator only runs ordinary rules when the key is present (and not an empty string) or the rule is implicit. A missing key is not present, so `string` is skipped; a `null` value is present, so `string` runs and fails unless `nullable` is also set.
  • Does sometimes help on a normal HTML form text input?
    Rarely. Browsers submit every text input, even when blank, so the key is always present and `sometimes` never skips anything. It matters for JSON APIs and PATCH requests where clients omit keys, and for checkboxes, which are not sent when unchecked.

Think of a hotel check-in form: sometimes is "only look at the parking section if they filled in that page at all", nullable is "a blank licence plate is fine", and present is "the parking page must be handed in, even if blank".

saying these in an interview costs you the question

  • Without nullable, an omitted optional key fails its string rule
  • sometimes skips ordinary rules but still enforces required
  • Ordinary rules are skipped when a field is sent as null
  • present fails when the value is null
  • ConvertEmptyStringsToNull is opt-in and off by default
open as a page

In Laravel, how do you validate that a username is unique on a profile-edit form without rejecting the user's own current username?

level: middleimportance: must knowfreq 72%

basics

~20 s

Use Rule::unique('users', 'username')->ignore($user), passing the authenticated or route-bound model so its own row is excluded from the check. Take the ignored id from the server, never from request input, and keep a unique index in the database.

open as a page

In Laravel validation, when must rules be written as an array instead of a pipe-delimited string, and what does the bail rule change?

level: juniorimportance: should knowfreq 45%

basics

~20 s

A pipe string is split on |, so a regex containing a pipe, a closure, or a rule object without a string form such as Password::defaults() or File::types() must go in an array. bail stops running a field's remaining rules after its first failure.

open as a page

In Laravel validation, how do required_if, exclude_if and Rule::when differ when a field depends on another field's value?

level: middleimportance: should knowfreq 42%

basics

~20 s

required_if makes a field mandatory when another field has a value but still validates and keeps it otherwise. exclude_if skips the field entirely and drops it from validated(). Rule::when picks one of two rule sets from a boolean or closure evaluated at validation time.

open as a page

In Laravel validation, how do you validate every element of a submitted array and keep unexpected nested keys out of the validated data?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Give the parent 'array' (optionally with allowed keys, array:label,url) and write rules per element with the * wildcard, such as links.*.url. When child rules exist, validated() returns only validated nested keys; a bare array rule passes the whole array through.

open as a page

In Laravel, how do you write a custom validation rule class, and when does it run for a missing or empty field?

level: seniorimportance: should knowfreq 45%

basics

~20 s

Run php artisan make:rule to get a class implementing ValidationRule with validate(string $attribute, mixed $value, Closure $fail); call $fail() with a message to reject. Like ordinary rules, it is skipped for absent or empty-string fields unless it sets public $implicit = true.

open as a page