Advanced Laravel Validation: Conditional Rules with Custom Closures

📖 2 minutes read

When building complex forms in Laravel, you’ll often need validation rules that depend on other input values. Laravel’s Rule::requiredIf() combined with custom closure validation gives you powerful control over conditional logic.

The Challenge

Imagine you’re building a file upload system where users can choose between uploading files or entering barcodes. The validation rules need to change based on that choice—files are required for one mode, barcodes for another. Hardcoding separate validation paths leads to duplication and brittle code.

The Solution: Conditional Rules with Closures

Laravel lets you build validation rules dynamically using Rule::requiredIf() for conditional requirements and custom closures for complex business logic:

use Illuminate\Validation\Rule;

$uploadType = $request->input("items.{$itemId}.upload_type");

$rules = [
    "items.{$itemId}.upload_type" => 'required|in:file,barcode',
    
    // Files only required when upload_type is 'file'
    "items.{$itemId}.files" => [
        "required_if:items.{$itemId}.upload_type,file",
        'array',
        function ($attribute, $value, $fail) use ($itemId, $maxAllowed) {
            if (count($value) > $maxAllowed) {
                $fail("Item {$itemId}: Too many files. Max allowed: {$maxAllowed}");
            }
        }
    ],
    
    // Barcodes only required when upload_type is 'barcode'
    "items.{$itemId}.barcodes" => [
        "required_if:items.{$itemId}.upload_type,barcode",
        'string',
        function ($attribute, $value, $fail) use ($repository, $itemId) {
            $codes = array_filter(preg_split('/[\s\n]+/', $value));
            
            // Check for duplicates in database
            $duplicates = $repository->findExisting($codes);
            if ($duplicates->isNotEmpty()) {
                $fail("Item {$itemId}: Duplicate barcodes found: " . $duplicates->implode(', '));
            }
        }
    ],
];

$validated = $request->validate($rules);

Why This Pattern Works

  • Centralized validation: All rules in one place, no scattered if/else branches
  • Flexible conditions: Rule::requiredIf() handles simple dependencies
  • Custom business logic: Closures let you inject services and run complex checks
  • Clear error messages: Customize failures per field and context

Taking It Further

You can nest conditions deeper with Rule::when() or combine multiple closure validators for different aspects (format validation, uniqueness, business rules). Laravel’s validation system is expressive enough to handle even the most complex form requirements without leaving the validation layer.

Pro tip: For very complex validation, consider extracting to a custom Form Request class. But for moderately complex interdependent fields, this inline approach keeps everything readable and maintainable.

Daryle De Silva

VP of Technology

11+ years building and scaling web applications. Writing about what I learn in the trenches.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *