Skip to content

Validating with a RuleSet

validate() and check() are the terminal calls on a RuleSet. This page covers both, and the per-call options that chain before them.

validate() throws ValidationException on failure; check() returns the outcome as data. Both take an array or an Illuminate\Http\Request, and passing the request keeps the $request->all() read inside the library, which satisfies static-analysis rules that flag unsafe input access. failOnUnknownFields, dropUnknownFields, stopOnFirstFailure and withBag chain before either terminal call.

Errors-as-data with check()

For import pipelines, batch jobs, and any flow where an exception is the wrong control structure. Returns an immutable Validated:

php
use SanderMuller\FluentValidation\RuleSet;

foreach ($rows as $row) {
    $result = RuleSet::from($rules)->check($row);

    if ($result->fails()) {
        Log::warning('row rejected', $result->errors()->all());
        continue;
    }

    $safe = $result->safe();        // Illuminate\Support\ValidatedInput, gives you ->only(), ->except(), ->collect()
    $array = $result->validated();  // plain array (throws if the result failed)
    insert_row($safe->all());
}
MethodReturnsDescription
->passes()boolDid validation pass?
->fails()boolInverse of passes()
->errors()MessageBagAll validation errors (empty bag on success)
->firstError($field)?stringFirst error message for a field, or null
->validated()arrayValidated data; throws ValidationException if it failed
->safe()ValidatedInputSame data as validated(), wrapped for ->only()/->except()/->collect() access
->validator()ValidatorContractEscape hatch for deep Laravel integration (->after(), ->sometimes(), extensions)

check() runs the same engine as validate() (fast-check closures, wildcard expansion, batched DB queries) and wraps the outcome rather than re-parsing.

Rejecting unknown fields

failOnUnknownFields() rejects input keys that don't match any rule in the set. If someone sends role when you only defined name and email, validation fails:

php
$validated = RuleSet::from([
    'name'  => FluentRule::string()->required(),
    'email' => FluentRule::email()->required(),
])->failOnUnknownFields()->validate($request->all());
// Input: ['name' => 'John', 'email' => 'john@example.com', 'role' => 'admin']
// → ValidationException: "The role field is prohibited."

Wildcard arrays are checked too. items.0.hack fails if only items.*.name is defined. You can customize the error message per field:

php
->validate($data, messages: ['role.prohibited' => 'This field is not allowed.']);

TIP

For form requests, Laravel 13.4+ has a native #[FailOnUnknownFields] attribute that works automatically with HasFluentRules.

Silently dropping unknown fields

dropUnknownFields() is the lenient counterpart to failOnUnknownFields(): instead of rejecting unknown keys, it strips them from the validated() output. Top-level keys outside the rule set are already excluded; this flag extends the same behavior to nested array shapes declared via children(), each(), or dotted rule keys:

php
$validated = RuleSet::from([
    'name' => FluentRule::string()->required(),
    'meta' => FluentRule::array()->required()->children([
        'type' => FluentRule::string()->required(),
    ]),
])->dropUnknownFields()->validate($request);
// Input:  ['name' => 'John', 'meta' => ['type' => 'admin', 'secret' => 'leak']]
// Output: ['name' => 'John', 'meta' => ['type' => 'admin']]

If both dropUnknownFields() and failOnUnknownFields() are set, failOnUnknownFields() wins: unknown keys trigger a validation error before the drop ever applies.

Stopping on first failure

stopOnFirstFailure() bails after the first field error. If the file upload fails, the 500 exists queries for items never run:

php
$validated = RuleSet::from([
    'file'   => FluentRule::file()->required()->max('10mb'),
    'items'  => FluentRule::array()->required()->each([
        'sku' => FluentRule::string()->required()->exists('products', 'sku'),
    ]),
])->stopOnFirstFailure()->validate($request->all());

The same applies inside wildcard arrays. If the first item fails, the rest are skipped.

Named error bags (withBag)

Multiple forms on one page (Fortify's update-password + reset-password, a Livewire multi-card screen, etc.) need separate error bags so their validation errors don't collide. Chain ->withBag($name) on the rule set; the thrown ValidationException's errorBag is set to that name:

php
RuleSet::from([
    'current_password' => FluentRule::string()->required()->currentPassword(),
    'password'         => FluentRule::string()->required()->min(12),
])
    ->withBag('updatePassword')
    ->validate($input);

Mirrors Laravel's Validator::validateWithBag() without forcing you back to the Validator::make(...) incantation. Only affects the thrown exception's bag; check() never throws and is unaffected.