phplaravellaravel-5laravel-8laravel-exceptions

Manually throw Laravel ValidationException VS FormRequestValidationException


Is there any difference when I throw ValidationException manally and when ValidationException is thrown by laravel from FormRequest.

Following code will clear the problem

UserController.php

public function checkEmailExists(Request $request){
    try {
        $validation =  Validator::make($request->only('email'), [
            'email' => 'required|email|exists:users,email',
        ]);
        if ($validation->fails()) {
            throw (new ValidationException($validation));
        }
    } catch (\Exception $exception){
        return $exception->render(); //nothing is returned/displayed
    }
}

Handler.php

public function render($request, Throwable $exception)
{
    dd($exception instanceof Exception);
}

From the UserController I am throwing ValidationException manually and in Handler.php render method I am checking the $exception is an instance of Exception. So If I throw ValidationException manually then

dd($exception instanceof Exception); //gives false

But when I use UserStoreRequest (FormRequest)

UserController.php

public function checkEmailExists(UserStoreRequest $request){
    //Exception thrown by laravel if validation fails fro UserStoreRequest
}

then in Handler.php render() method

dd($exception instanceof Exception); //gives true

1:- Why it has different behavior when I throw ValidationException manally and when ValidationException is thrown by laravel from FormRequest?

2:- If I throw ValidationException manually then in the catch block I get the following error

Error {#3627
  #message: "Call to undefined method Illuminate\Validation\ValidationException::render()"
  #code: 0
  #file: "myproject/app/Http/Controllers/UserController.php"
  #line: 33

Solution

  • dd($exception instanceof Exception); //gives false
    

    I'm pretty sure this is impossible. Illuminate\Validation\ValidationException extends directly from Exception. Your result might have to do with the catch block in checkEmailExists. Just so you know, you shouldn't have to catch such exceptions inside your controller, since that is what the Exception Handler is for (app/Exceptions/Handler.php).

    There shouldn't be any different behaviour on how you use this, so let me show what ways you would use such validation:

    Inside a controller function

    Inside controllers you have the helper $this->validate(...) available:

    public function index(\Illuminate\Http\Request $request) {
        $this->validate($request, [
            'test' => 'required|integer'
        ], [
            'test.integer' => 'Some custom message for when this subvalidation fails'
        ]);
    }
    

    This automatically throws a ValidationException and therefore should get picked up by your Exception handler. Your Exception handler will then decide whether to return a JSON response with the validation errors (this will happen when a header Accept: application/json was used for instance) or flash messages to the session such that you can show them in your templates.

    Outside controllers

    Sometimes it is extremely handy to use the validation for things that run outside of your controllers. These could be jobs or background tasks for instance. In those cases, you would call it like so (its basically the thing what happens in the controller function):

    class SomeLibrary
    {
        public function doSomething() {
            // Quickest way:
            \Illuminate\Support\Facades\Validator::make($data, [
                'test' => 'required|integer'
            ])->validate();
    
            // Convoluted way:
            // (see your own code in the original post)
            
        }
    }
    

    This syntax basically does the same thing and also throws a ValidationException.

    Throw a validation error immediately

    Lastly, in some cases you want to immediately throw a validation exception without even needing to test any input, in that case you can use this to set the error messages:

    throw \Illuminate\Validation\ValidationException::withMessages([
        'amount' => 'The amount is not high enough'
    ]);
    

    This will then follow the same route through the exception handler.