This commit introduces a mechanism for validators to return early once the validation outcome is determined, rather than evaluating all child validators. The ShortCircuit validator evaluates validators sequentially and stops at the first failure, similar to how PHP's && operator works. This is useful when later validators depend on earlier ones passing, or when you want only the first error message. The ShortCircuitCapable interface allows composite validators (AllOf, AnyOf, OneOf, NoneOf, Each, All) to implement their own short-circuit logic. Why "ShortCircuit" instead of "FailFast": The name "FailFast" was initially considered but proved misleading. While AllOf stops on failure (fail fast), AnyOf stops on success (succeed fast), and OneOf stops on the second success. The common behavior is not about failing quickly, but about returning as soon as the outcome is determined—which is exactly what short-circuit evaluation means. This terminology is familiar to developers from boolean operators (&& and ||), making the behavior immediately understandable. Co-authored-by: Alexandre Gomes Gaigalas <alganet@gmail.com> Assisted-by: Claude Code (Opus 4.5)
2.8 KiB
ShortCircuit
ShortCircuit()ShortCircuit(Validator ...$validators)
Validates the input against a series of validators, stopping at the first failure.
Like PHP's && operator, it uses short-circuit evaluation: once the outcome is determined, remaining validators are
skipped. Unlike AllOf, which evaluates all validators and collects all failures, ShortCircuit returns
immediately.
v::shortCircuit(v::intVal(), v::positive())->assert(15);
// Validation passes successfully
This is useful when:
- You want only the first error message instead of all of them
- Later validators depend on earlier ones passing (e.g., checking a format before checking a value)
- You want to avoid unnecessary validation work
This validator is particularly useful in combination with Factory when later validations depend on earlier results. For example, validating a subdivision code that depends on a valid country code:
$validator = v::shortCircuit(
v::key('countryCode', v::countryCode()),
v::factory(static fn($input) => v::key('subdivisionCode', v::subdivisionCode($input['countryCode']))),
);
$validator->assert([]);
// → `.countryCode` must be present
$validator->assert(['countryCode' => 'US']);
// → `.subdivisionCode` must be present
$validator->assert(['countryCode' => 'US', 'subdivisionCode' => 'ZZ']);
// → `.subdivisionCode` must be a subdivision code of United States
$validator->assert(['countryCode' => 'US', 'subdivisionCode' => 'CA']);
// Validation passes successfully
Because SubdivisionCode requires a valid country code, it only makes sense to validate the
subdivision after the country code passes. You could achieve this with When, but you would have to repeat
v::key('countryCode', v::countryCode()) twice.
Note
The check() method in ValidatorBuilder uses ShortCircuit internally to short-circuit the entire validation chain. Use ShortCircuit directly when you need fine-grained control over which specific group of validators should fail fast, while letting the rest of the validation continue collecting errors via assert().
Templates
This validator does not have templates of its own. It returns the result of the first failing validator, or the result of the last validator when all pass.
Categorization
- Composite
- Conditions
- Nesting
Changelog
| Version | Description |
|---|---|
| 3.0.0 | Created |