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:
composer require sandermuller/laravel-fluent-validation
Requires PHP 8.2+ and Laravel 11+.
Basic Usage in Form Request:
Replace traditional validation strings with fluent builders in your rules() method:
use SanderMuller\FluentValidation\FluentRule;
use SanderMuller\FluentValidation\HasFluentRules;
class StorePostRequest extends FormRequest
{
use HasFluentRules;
public function rules(): array
{
return [
'title' => FluentRule::string('Title')->required()->min(2)->max(255),
'email' => FluentRule::email()->required()->unique('users'),
];
}
}
Key First Use Case: Convert a simple validation rule from string syntax to fluent syntax:
// Before
'name' => 'required|string|min:3|max:255',
// After
'name' => FluentRule::string('Name')->required()->min(3)->max(255),
Fluent Rule Chaining: Chain methods for each validation rule type (string, email, date, etc.):
FluentRule::string('Full Name')
->required()
->min(2)
->max(255)
->message('Must be between 2 and 255 characters')
Conditional Rules:
Use when() for dynamic rules:
FluentRule::string('Role')
->when($isAdmin, fn ($r) => $r->required()->in(['admin', 'editor']))
Array Validation:
Validate nested arrays with each() and children():
FluentRule::array('Items')
->each([
'id' => FluentRule::integer()->required(),
'name' => FluentRule::string()->max(255),
])
Database Rules:
Use unique() and exists() with closures for dynamic conditions:
FluentRule::email()
->required()
->unique('users', 'email', fn ($r) => $r->ignore($userId))
Form Requests:
Extend FluentFormRequest or use HasFluentRules trait:
class StorePostRequest extends FluentFormRequest { ... }
Custom Messages: Attach labels and messages directly to rules:
FluentRule::string('Title')->required()->message('Title is required')
Type Safety:
Use FluentRuleContract for return type hints:
/** @return array<string, FluentRuleContract> */
public function rules(): array { ... }
Performance Optimization:
For large arrays, use HasFluentRules trait for O(n) wildcard validation.
Testing:
Use FluentRulesTester for validation tests:
$this->validateRules($request, [
'title' => FluentRule::string()->required(),
]);
Static Factory Misuse:
FluentRule is a static factory, not a base class. Each type returns a specific rule class:
// Correct
FluentRule::string()->required();
// Incorrect (returns StringRule, not FluentRule)
FluentRule::string()->email(); // Throws error
Array Validation Scope:
each() applies to wildcard arrays (items.*), while children() applies to fixed keys:
// Wildcard (items.*.name)
FluentRule::array()->each(FluentRule::string()->max(255));
// Fixed key (items.fixed_key)
FluentRule::array()->children(['fixed_key' => FluentRule::string()]);
Message Desync:
Labels and messages are tied to the rule instance. Avoid separate attributes() or messages() arrays.
Performance Caveats:
HasFluentRules optimizes to O(n).exists/unique checks batch into single queries for wildcards.IDE Autocompletion:
Use IDE hints for available methods (e.g., FluentRule::string() won’t suggest digits()).
Rule Inspection:
Use RuleSet for debugging complex rules:
$ruleSet = new RuleSet($rules);
$ruleSet->inspect('title'); // Inspect a specific rule
PHPStan Errors:
Migration Issues:
Custom Rules:
Extend FluentRule or create macros:
FluentRule::macro('customRule', function () {
return $this->rule(['custom_rule']);
});
Livewire Integration:
Use HasFluentValidation trait for Livewire components:
use SanderMuller\FluentValidation\Livewire\HasFluentValidation;
Performance Tuning:
whenInput() for dynamic branching.rule() for Laravel-specific escape hatches (e.g., authorization).Static Analysis:
Enable the PHPStan rules package to catch unbounded each() chains.
How can I help you explore Laravel packages today?