sandermuller/laravel-fluent-validation
Type-safe, IDE-autocomplete Laravel validation rule builders. Create rules fluently without memorizing strings; each rule exposes only valid methods. Define nested array validation with each()/children(). Optional HasFluentRules trait speeds wildcard validation dramatically (up to 160x).
Installation: Add via Composer:
composer require sandermuller/laravel-fluent-validation
Requires PHP 8.2+ and Laravel 11+.
First Use Case: Replace a simple string validation in a Form Request:
use SanderMuller\FluentValidation\FluentRule;
public function rules(): array
{
return [
'name' => FluentRule::string('Full Name')->required()->min(2)->max(255),
];
}
Key Files to Explore:
src/FluentRule.php: Entry point for all rule builders.src/Contracts/FluentRuleContract.php: Base contract for type-hinting.src/HasFluentRules.php: Trait for Form Requests.Rule Chaining: Build rules fluently with IDE autocompletion:
FluentRule::email('Email Address')
->required()
->unique('users', 'email', fn ($r) => $r->ignore($id))
->message('Please provide a valid email.');
Array Validation:
each() for dynamic keys:
'items' => FluentRule::array()->each([
'id' => FluentRule::integer()->required(),
'name' => FluentRule::string()->max(255),
]),
children() for static keys:
'metadata' => FluentRule::array()->children([
'author' => FluentRule::string(),
'tags' => FluentRule::array(),
]),
Conditional Rules:
'role' => FluentRule::string()->when(
$isAdmin,
fn ($r) => $r->required()->in(['admin', 'editor']),
fn ($r) => $r->nullable()
),
Form Request Integration:
HasFluentRules trait or extend FluentFormRequest:
use SanderMuller\FluentValidation\FluentFormRequest;
class StorePostRequest extends FluentFormRequest { ... }
Testing:
FluentRulesTester for assertions:
$this->assertValidationRules(
FluentRule::string()->required(),
['name' => '']
)->fails();
HasFluentValidation trait for component validation.validate() in resources/forms to use FluentRule.HasFluentRules optimizes wildcard validation up to 160x faster via tree-walking.exists()/unique() checks into single whereIn queries for wildcard arrays.Static Factory Misuse:
FluentRule is a static factory, not a base class. Avoid instantiating it directly:
// ❌ Wrong
$rule = new FluentRule();
// ✅ Correct
$rule = FluentRule::string();
Type-Hinting:
FluentRuleContract for return type hints, not concrete classes:
/** @return array<string, FluentRuleContract> */
public function rules(): array { ... }
Nested Arrays:
each()) must be defined under their parent. Avoid mixing each() and children() for the same key:
// ❌ Ambiguous
'items' => FluentRule::array()->each([
'*.name' => FluentRule::string(), // Invalid syntax
]),
// ✅ Correct
'items' => FluentRule::array()->each([
'name' => FluentRule::string(),
]),
Database Rule Chaining:
unique()/exists() must return the rule, not a void:
// ❌ Silent failure
->unique('users', fn ($r) => $r->ignore($id))
// ✅ Correct
->unique('users', 'email', fn ($r) => $r->ignore($id))
Label Overrides:
'user' => FluentRule::object()->children([
'name' => FluentRule::string('User Name')->required(),
]),
Error Messages:
->message() or ->messageFor() for custom messages:
->message('The :attribute must be at least 2 characters.')
->messageFor('min', 'Custom min error.')
Validation Inspection:
RuleSet::build() to inspect rules before validation:
$ruleSet = RuleSet::build($this->rules());
dd($ruleSet->toArray());
Performance Profiling:
FluentValidation\Performance\Benchmark in config/fluent-validation.php to log validation times.PHPStan Integration:
sandermuller/laravel-fluent-validation-phpstan to detect unbounded each() chains:
composer require --dev sandermuller/laravel-fluent-validation-phpstan
Custom Rules:
FluentRule via macros:
FluentRule::macro('customRule', function () {
return Rule::custom(fn ($attribute, $value) => $value === 'secret');
});
FluentRule::string()->customRule()->required()
Rule Composers:
$emailRule = FluentRule::email()->required()->unique('users');
$rules['email'] = $emailRule;
Livewire/Filament:
public function rules(): array
{
return [
'field' => FluentRule::string()->when(
$this->isAdmin(),
fn ($r) => $r->required(),
fn ($r) => $r->nullable()
),
];
}
Migrating Legacy Code:
composer require --dev sandermuller/laravel-fluent-validation-rector
vendor/bin/rector process src --dry-run
Default Labels:
config/fluent-validation.php:
'default_labels' => [
'email' => 'Email Address',
],
Performance Mode:
optimize_for_performance in config for large datasets:
'optimize_for_performance' => env('FLUENT_VALIDATION_PERF_MODE', false),
Message Fallbacks:
'fallback_messages' => [
'custom' => 'The :attribute does not meet the requirements.',
],
How can I help you explore Laravel packages today?