Laravel 5.5 - Validate Multiple Form Request - at the same time
Asked Answered
C

8

10

The question is already asked here for a previous version of laravel and not yet answered.

I have a html form which is validated using three different Form Request Validations. I am able to do this. But, the problem is, the form validations take place one by one. Not at the same time.

If the first form request throws a validation error the form is returned to the view so rest of the two forms doesn't evaluated, hence a proper validation error can't be displayed to the user.

What I want is : validate the form with the three form validation requests rules at the same time.

Controller:

public function store(TransportationRequest $request1, PurchaseRequest $request2, SaleRequest $request3)
    {
        //do actions here
    }

I have tried with inheriting the form requests one by one but could not be succeeded.

Edit :

To be more specific to my question:

I do have three seperate forms for purchase, transporataion and sale which are individually valuated using PurchaseRequest, TransportationRequest and SaleRequest for individual operations.

But there is a special case where a single form handles a purchase, transporataion and a sale. I want to validate the form using combining the three form request rules because I didn't want to write the same validation rules again.

This

Note : The fields in the seperate forms and combined form are same.

Thanks..

Charlinecharlock answered 21/1, 2018 at 19:35 Comment(6)
Why do you need 3 separate requests? The idea behind a request is that it maps to an incoming HTTP request, of which there is only one. That being said, you could (I think - not tried it myself) create another request object, that accepts your 3 requests as parameters to its constructor. Laravel's IoC container should be able to pass each request item to the constructor and then you can use each request instance to build a single set of rules and messages etc.Mcconnell
My question is how do you manage to post 3 forms at the same time or is it just different requests to split a single form in 3 parts? The answer to the original question is you need to manually make the calidator and run the passes() method on each one and then collect the errors as described hereGile
Can you give an example of when you would need separate firm request classes? The question you linked doesn't make sense with the multiple request parameters. I've never come across an instance where one firm request wasn't adequate .Vichy
@jonathon, I have also tried that way but it also returns to view after passing the first request to the constructor. I am not familiar with Laravel's IoC container and not able to find it in docs(5.5).Charlinecharlock
@Gile question updated. I wanted to keep the validation logic separated to form requests that's why I don't use the manual validatorCharlinecharlock
@btl question is updated with sample explanations.Charlinecharlock
T
16

A FormRequest throws an Illuminate\Validation\ValidationException Exception when validation fails which has a redirectTo method, and from there the Exception Handler performs the redirect.

You can achieve your desired behaviour by running your Form Requests manually in your controller within a try/catch block which captures the errors and combines the error bags before redirecting, or if it's essential that you run them by Laravel injecting them into your controller then you would need to add your own exception handler which captures all of the errors, combines them and then redirects after the final Form Request has ran.

However, it's worth noting, both of those approaches aren't great: they're cumbersome and are going to cause you more problems than they solve. You should try to adhere to the Laravel way of doing things as best possible if you'd like to write a maintainable application.

A Form Request exists to validate a form, therefore, each Form should have one Form Request, if you wish to compose a Form Request from different sets of rules then that should be done within the Form Request, e.g:

  1. Define your Form Request for your form php artisan make:request StoreMultipleForm
  2. From the rules method on StoreMultipleForm fetch the rules for each of the other Form Requests and then return them together, e.g:

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $formRequests = [
          TransportationRequest::class,
          PurchaseRequest::class,
          SaleRequest::class
        ];
    
        $rules = [];
    
        foreach ($formRequests as $source) {
          $rules = array_merge(
            $rules,
            (new $source)->rules()
          );
        }
    
        return $rules;
    }
    
  3. Use the new composed Form Request in your controller, e.g:

    public function store(StoreMultipleForm $request)
    {
        // Do actions here.
    }
    

The advantages of this method are that it's self-contained, it adheres to the one form one Form Request expectation, it doesn't require changes to the Form Requests you're combining and if you need to add additional rules unique to this form you can do so without creating another Form Request.

Tribble answered 21/1, 2018 at 20:26 Comment(1)
You wrote a great answer but to keep it complete I think you need to call authorize function on each.Labiovelar
V
3

I would create traits containing the rules for each FormRequest - purchase, transporataion and sale. Use the trait in it's specific FormRequest and then when you need all the rules you can use all three traits in the combined FormRequest and merge the rules arrays then.

Vichy answered 21/1, 2018 at 20:21 Comment(1)
You are going to have ambiguous method declarations when multiple traits with the rules() methods are used in the same form request class.Egger
M
2

If I understand correctly, you have 3 forms, each with their own form requests to deal with their respective validation. You also have another form which combines those 3 forms somewhere else and you don't want to repeat yourself by rewriting those validation rules.

In which case, I would still suggest going with a single form request, but try to combine the rules of each of those individual requests. For example, you use static methods to define your rules on the 3 individual form requests and have each individual request call its own static method to grab them:

class TransportationRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

class PurchaseRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

class SaleRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

And then have your combined request merge all three sets:

class CombinedRequest extends FormRequest
{
    public function rules()
    {
        return array_merge(
            TransportationRequest::getRules(),
            SaleRequest::getRules(),
            PurchaseRequest::getRules()
        );
    }
}

Then you can use the single CombinedRequest in your controller method. Of course, if you don't like the static method approach, in your combined request rules method you could just new up each individual request and call the rules method on each of them and merge the results.

class CombinedRequest extends FormRequest
{
    public function rules()
    {
        $transportation = (new TransportationRequest())->rules();
        $sale = (new SaleRequest())->rules();
        $purchase = (new PurchaseRequest())->rules();

        return array_merge(
            $transportation,
            $sales,
            $purchase
        );
    }
}
Mcconnell answered 21/1, 2018 at 20:27 Comment(0)
D
2

Turns out if you resolve the request, validation will kick in.
One caveat of this is that all the validation objects will not kick in at once but depending on your use case, this might be simpler.

    public function store()
    {
        // request()->replace([ some fields to be validated ]);
        resolve(TransportationRequest::class);
        resolve(PurchaseRequest::class);
        resolve(SaleRequest::class);
        // request is valid
    }
Decolorize answered 22/9, 2020 at 14:38 Comment(1)
it should be an accepted answer but the answer may be improved by adding try{..} catch(Illuminate\Validation\ValidationException $ex){...} on every resolve and the resolve should be replaced with app :)Stiegler
S
2

You can manually execute Request classes one by one:

public function store()
{
    $isValid = true;
    try{
        app(TransportationRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    try{
        app(PurchaseRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    try{
        app(SaleRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    if (!$isValid){
        throw $ex;
    }
}

If validation in one of them will fail, a user will be redirected back to previous page with an error.

Stiegler answered 21/6, 2021 at 5:20 Comment(0)
S
1

You could concatinate all rules and validate manually:

$allRules = (new TransportationRequest)->rules() + (new PurchaseRequest)->rules() + (new SaleRequest)->rules();
Validator::make($request->all(), $allRules)->validate();
Shannashannah answered 21/1, 2018 at 20:30 Comment(0)
E
1

I know this is a pretty old question, but I got annoyed by not being able to chain form requests together so I made a composer package for this, so you don't have to.

https://github.com/sharpie89/laravel-multiform-request

Emmanuelemmeline answered 28/1, 2020 at 17:6 Comment(0)
D
0

I recently came up against this problem, and solved it like this:

public function rules()
{
    $request1 = RequestOne::createFrom($this);
    $request2 = RequestTwo::createFrom($this);

    return array_merge(
        $request1->rules(),
        $request2->rules()
    );
}
Dunaville answered 7/5, 2020 at 9:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.