On a manually built Laravel validator, what do the after(), sometimes() and stopOnFirstFailure() methods add, and when does each take effect?
answer
- configure before calling fails()
- after(): callables run after all rules
- sometimes($field, $rules, fn (Fluent $input))
- sometimes closure runs when you call it
- stopOnFirstFailure(): stop at first failing field
basics
~20 safter() registers callbacks that run once the rules finish, to add cross-field errors. sometimes() adds rules to fields only when a closure over the input returns true. stopOnFirstFailure() stops checking further fields after the first failure. All are set before fails() or validate().
solid answer
~30 sAll three are called on the instance returned by `Validator::make()` before evaluating it. `after($callback)` accepts a closure, an invokable object or an array of them; each receives the `Validator` after the rules have run — even if they failed — and can call `$validator->errors()->add()`. `sometimes($attribute, $rules, $callback)` adds `$rules` to one or more fields when the callback, given the data as an `Illuminate\Support\Fluent` (and for wildcard fields the current item), returns true; note it evaluates the callback immediately, against the data passed to `make()`. `stopOnFirstFailure()` makes the validator break out after the first field that fails; `bail` is the per-field version.
code
php · 22 lines<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Fluent;
$validator = Validator::make($row, [
'price' => ['required', 'numeric', 'min:0'],
'sale_price' => ['nullable', 'numeric', 'min:0'],
'discount' => ['nullable', 'integer', 'between:0,100'],
]);
$validator->sometimes('discount_reason', ['required', 'max:200'], function (Fluent $input) {
return $input->discount > 30;
});
$validator->after(function ($v) use ($row) {
if ($v->errors()->isEmpty() && ($row['sale_price'] ?? 0) > $row['price']) {
$v->errors()->add('sale_price', 'Sale price cannot exceed price.');
}
});
$bad = $validator->stopOnFirstFailure()->fails();go deeper
Know that after() adds extra checks and sometimes() adds rules under a condition.
Explain the callback signatures, that after() runs even on failure, and stopOnFirstFailure() versus bail.
Account for sometimes() evaluating at call time and after() needing guards, and choose between sometimes() and Rule::when().
Keep conditional validation readable by choosing one place for conditions, so reviewers do not trace rules across construction steps.
## Three configuration methods `Validator::make()` returns an unevaluated `Illuminate\Validation\Validator`. Before you call `fails()`, `passes()` or `validate()`, three methods let you adjust how it will run. ## after(): checks that span fields or need a query ```php $validator->after(function (Validator $validator) use ($row) { if ($row['sale_price'] > $row['price']) { $validator->errors()->add('sale_price', 'Sale price cannot exceed price.'); } }); ``` - Accepts a closure, an invokable object, or an **array** of them (each receives the validator). - Callbacks run **after all rules**, whether or not the rules passed — so guard against missing or malformed values, for example by returning early when `$validator->errors()->isNotEmpty()`. - Adding an error in a callback makes `fails()` return `true`. ## sometimes(): conditional rules on a manual validator ```php $validator->sometimes('discount_reason', ['required', 'max:200'], function (Fluent $input) { return $input->discount > 30; }); ``` - First argument: one field or an array of fields. - Second: rules to add when the condition holds. - Third: a callback receiving the data as `Illuminate\Support\Fluent`. For a wildcard field like `lines.*.reason`, it also receives the **current item** as a second argument, so each element can be judged on its own values. Two things are easy to miss: 1. The callback runs **when you call `sometimes()`**, against the data given to `make()`. If you change the data later (with `setData()`), the decision is not re-evaluated. 2. The method and the `sometimes` **rule** share a name but not a meaning: the rule means "only validate when the key is present"; the method means "add these rules when this closure says so". ## stopOnFirstFailure(): stop at the first bad field ```php if ($validator->stopOnFirstFailure()->fails()) { /* ... */ } ``` - Before moving to the next field, the validator checks whether any error exists and stops if so. - `after()` callbacks still run afterwards. - It differs from the `bail` rule, which stops the remaining rules **of one field**. Use it when you only need to know that the data is bad — for example rejecting a CSV row quickly — and the full list of problems is not shown to anyone. ## Timing summary | Method | When its logic runs | Effect | |---|---|---| | `after($cb)` | during `passes()`, after all rules | callbacks may add errors | | `sometimes($f, $r, $cb)` | immediately, at the call | adds rules to the validator if `$cb` is true | | `stopOnFirstFailure()` | during `passes()` | stops iterating fields after the first error | ## Putting them together for an import row 1. `Validator::make($row, $baseRules)` 2. `->sometimes('discount_reason', 'required', fn ($in) => $in->discount > 30)` 3. `->after(fn ($v) => $this->checkCategory($v, $row))` 4. `->stopOnFirstFailure()` 5. `->fails()` — one boolean per row, with at most one field reported. ## Versus other places for the same logic - In a form request, the equivalents are its `after()` method and its stop-on-first-failure setting; that is where they belong when validating a request. - `Rule::when()` inside the rules array can often replace `sometimes()` and keeps the condition next to the field; `sometimes()` is handy when you add conditions after construction or across several fields at once. ## Pitfalls - Calling `sometimes()` after `fails()` has no effect on the result already computed; configure first. - Expecting `stopOnFirstFailure()` to skip `after()` callbacks — they still run. - Returning a boolean from an `after` callback expecting it to fail validation; only added errors count. ## How to explain the difference quickly `sometimes()` decides **which rules exist**, at the moment you call it. `after()` adds **extra checks** after the rules run. `stopOnFirstFailure()` decides **how far** the rule loop goes. Keeping those three verbs apart — which, extra, how far — is usually enough to answer the follow-ups.
- Why might a sometimes() condition ignore data you set later with setData()?`sometimes()` evaluates its callback immediately against the validator's current data and adds the rules then. Changing the data afterwards does not re-run the callback, so call `sometimes()` after the data is final, or build a new validator.
- Does stopOnFirstFailure() stop after() callbacks from running?No. It only breaks out of the loop over fields. `after()` callbacks still run once that loop ends, so a callback should check whether errors already exist if it depends on valid data.
A manual validator is like a building inspector's checklist: sometimes() adds extra checklist lines for certain buildings before the visit, stopOnFirstFailure() means skipping the remaining rooms once one fails, and after() is the final walk-through of how the parts fit together, which still happens either way.
saying these in an interview costs you the question
- after() callbacks only run when every rule passed
- The sometimes() method and the sometimes rule do the same thing
- stopOnFirstFailure() stops the remaining rules of one field only
- sometimes() callbacks are re-evaluated every time fails() is called