Miguel Miller
Miguel Miller

Reputation: 41

Validate a field only if another one passes validation (in Laravel 8)

I have a problem, how do you add / remove a rule if another rule passed/fails?

public function rules()
{
    $rules = [
        'description' => 'required|unique:orders,description',
        'user_id' => 'required|unique:users,id',
        'supplier_id' => 'required',
        'warehouse_id' => 'required|unique:warehouse,id',
        'warehouse_qty' => 'required'


    ];
    
    return $rules;
}

For example, if the "warehouse_id" rule fails, it would not be necessary to evaluate the "warehouse_qty" field's rules (since the warehouse id is not found in the db).

Or if the "user_id" field's rule succeeds, new rules should be activated, like for "user_name" and "user_phone" fields.

Thanks!

Upvotes: 2

Views: 2836

Answers (2)

Top-Master
Top-Master

Reputation: 8751

To validate a field only if another passes validation, there are multiple solutions.

Approach #2 (recommended)

Since Laravel 5.3.23; Simply move any field validation that depends on another field's rules, into Controller's withValidator(...) method, like:

use Illuminate\Support\Facades\Validator;

// ...

public function withValidator($validator)
{
    $validator->after(function ($validator) {
        // Skip if any previous field was invalid.
        if ($validator->failed()) return;

        // Actual validation (for fields depending on previous).
        Validator::make($this->input(), [
            'warehouse_qty' => ['required']
        ])->validate();
    });
}

This has the advantage of simply using existing rules, as you would normally (instead of rewriting the "required" rule's source-code, which the below approach does).

See also related Laracast post.

Note that if rules() method is not defined, withValidator(...) is never called (and calling $this->input() may result in function not defined error).

Approach #1 (Old answer)

Since Laravel 5.6 and later we can do below:

use Illuminate\Support\Facades\Validator;

// ...

public function rules()
{
    $result = [
        // ...
        'warehouse_id' => 'required|unique:warehouse,id',
    ];

    if (Validator::make($this->input(), $result)->passes()) {
        $result['warehouse_qty'] = [
            function ($attribute, $value, $fail) {
                if (empty($value)) {
                    $fail('The '.$attribute.' is required.');
                }
            },
        ];
    }

    return $result;
}

Note that for above to work, the fields order is important, and the closure should be added to array right after the fields it is depending on.

-------===-------

While above are custom approaches (for asked matter), doing that for rules of the same field is built-in Laravel.

  1. Adding a rule if another rule passed is easy:
    • The rules are executed from left to right.
    • Use pipe (the | character) to separate rules.
    • And, as long as rules at left pass, the later rules are added and/or executed.
  2. Removing a rule if another rule fails can be achieved by pipe as well.

    Simply because any rule after the failed one is ignored.

  3. But the reverse of above two statements is not possible:
    • We can not add and/or execute next-rule if previous fails.
    • We can not remove next-rule if previous passed.
    • At least, not without custom function.

Upvotes: 3

Miguel Miller
Miguel Miller

Reputation: 41

Thanks to all who gave their solutions to my problem, especially Top-Master, since his explanation showed me another point of view.

I was finally able to solve my problem, and this is my solution. If anyone can improve the code, it is welcome.

public function rules()
{   

    $rules = [
        'description' => 'required|unique:orders,description',
        'user_id' => 'required|unique:users,id',
        'supplier_id' => 'required'
    ];
  
    $rules = $this->validateItems($rules);
    
    return $rules;
}

public function validateItems($rules){


        $rules["warehouse_id"] = ['required', 
                                  'unique:warehouse,id'];
        
        //Here I validate if the rules of the "warehouse_id" field fail or not.
        $validator = Validator::make($this->input('warehouse_id'), [
            "id" => ['required', 
                     'unique:warehouse,id']
        ]);

        //If the validation doesn't fail, the "warehouse_qty" field is added to $rule                                 
        if(!($validator->fails())){
            $rules["warehouse_qty"] = ['required'];
        }
        
    }
    return $rules;
}

Upvotes: 0

Related Questions