Disable request validation redirect in Laravel 5.4
Asked Answered
C

7

31

So I'm trying to develop a rest API for an internal project, and I've got an issue where when the form request validation fails, it shows the @index response.

So I have two routes;

Route::get('/api/clients', 'ClientController@index');
Route::post('/api/clients', 'ClientController@store');

@index lists all clients, @store creates a new client and I've got a Form Request Validator on the @store method which checks a name is provided for the client.

What I want is when the validator fails, it shows a JSON response with the validation errors. But what I think it happening, is the validation fails, so it redirects back to the same page, but the redirect is GET instead of POST, so it lists all the clients instead.

I know that you can set your headers so that it looks like an ajax request, in which it will show the JSON response properly, but I want it to show the JSON response regardless of whether it's ajax or not.

I've tried overriding the response method in my validator which didn't work, I've tried setting the wantsJson method in the validator to return true which again didn't work.

Help would be very much appreciated.

Code is below...

web.php

Route::get('/api/clients', 'ClientController@index');
Route::get('/api/clients/{client}', 'ClientController@show');
Route::post('/api/clients', 'ClientController@store');
Route::put('/api/clients/{id}', 'ClientController@update');
Route::delete('/api/clients/{id}', 'ClientController@delete');

ClientController.php

namespace App\Http\Controllers;

use App\Client;
use App\Http\Requests\ClientRequest;

class ClientController extends Controller
{

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(ClientRequest $request)
    {
        return Client::create([
            'title'   => request('title'),
            'user_id' => auth()->id()
        ]);
    }

ClientRequest.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ClientRequest extends FormRequest
{

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title' => 'required'
        ];
    }

    /**
     * Get the failed validation response for the request.
     *
     * @param array $errors
     * @return JsonResponse
     */
     public function response(array $errors)
     {
         dd('exit'); // Doesn't work
     }
}
Corded answered 21/9, 2017 at 17:52 Comment(0)
B
81

You can try like this

Include use first as below in your form request

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;

and then

protected function failedValidation(Validator $validator) {
        throw new HttpResponseException(response()->json($validator->errors(), 422));
    }

now if you try to validate then it will return like

{
"title": [
"The  title field is required."
]
}
Baldric answered 21/9, 2017 at 18:21 Comment(3)
exactly what im looking for!Batch
It's so refreshing to see a well thought out Laravel question online and an answer to match!Medicament
Throwing an error is a great idea. Thank you :)Ichthyo
B
78

When making the request we should send header info.

Accept: application/json
Content-Type: application/json

That's it, now laravel will not redirect and send the error message as JSON.

Branham answered 5/10, 2018 at 12:4 Comment(2)
yeah, this is what I'm facing right now when using postman. There is no error message when validate failed. it just redirects to the index page. thanks, man...Varistor
You can set in your collection's pre-request scripts to automatically set the Accept header: pm.request.headers.upsert({ key: 'Accept', value: 'application/json' })Cantata
S
21

Try this

Open app/Exceptions/Handler.php file

Include use

use Illuminate\Validation\ValidationException;

and then add method

    /**
     * Create a response object from the given validation exception.
     *
     * @param  \Illuminate\Validation\ValidationException  $e
     * @param  \Illuminate\Http\Request  $request
     * @return \Symfony\Component\HttpFoundation\Response
     */
    protected function convertValidationExceptionToResponse(ValidationException $e, $request)
    {
        if ($e->response) {
            return $e->response;
        }

        return response()->json($e->validator->errors()->getMessages(), 422);
    }

now you can get standard validationFailure response like ajax request

Stenger answered 19/2, 2018 at 11:14 Comment(0)
A
8

There is two ways to work with validator errors, my suggestion is second way:

1. First way, Simply return an error when validation fail's(in controller). Example:

    try {
        request()->validate([
            'input1' => 'required',
            'input2' => 'string|min:5',
        ]);

    } catch (\Illuminate\Validation\ValidationException $e){
        return response('The given data was invalid.', 400);

    }

Handy and clean.

2. Second way is show full errors to user(in controller), like this:

    use Illuminate\Support\Facades\Validator;

    $validator = Validator::make(request()->all(), [
         'id' => 'required|integer',
         'description' => 'string'
    ]);

    // return array of errors to client with status code 400
    if ($validator->fails())
        return response($validator->messages()->toArray(), 400);
Aridatha answered 19/6, 2019 at 14:41 Comment(0)
C
6

Simply use this trait to prevent redirect after FormRequest validation. The following trait is also brings some useful public methods, such as:

  • validatorPasses()
  • validatorFails()
  • validatorErrors()
  • respondWithErrorsJson(int $code = 422)
  • redirectWithErrors() - restores the default Laravel FomrRequest behavior

Trait

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationException;

trait PreventsRedirectWhenFailedTrait
{
    /**
     * Default self::failedValidation() Laravel behavior flag.
     *
     * @var bool
     */
    private $defaultFailedValidationRestored = false;

    /**
     * Check for validator success flag.
     *
     * @return bool
     */
    public function validatorPasses(): bool
    {
        return !$this->validatorFails();
    }

    /**
     * Check for validator fail flag.
     *
     * @return bool
     */
    public function validatorFails(): bool
    {
        return $this->getValidatorInstance()->fails();
    }

    /**
     * @return MessageBag
     */
    public function validatorErrors(): MessageBag
    {
        return $this->getValidatorInstance()->errors();
    }

    /**
     * Respond with validator errors in JSON format.
     *
     * @param  int  $code
     */
    public function respondWithErrorsJson(int $code = 422): void
    {
        if ($this->validatorFails()) {
            throw new HttpResponseException(
                response()->json(['errors' => $this->getValidatorInstance()->errors()], $code)
            );
        }
    }

    /**
     * Restore and apply default self::failedValidation() method behavior.
     *
     * @throws ValidationException
     */
    public function redirectWithErrors(): void
    {
        $this->defaultFailedValidationRestored = true;

        $this->failedValidation($this->getValidatorInstance());
    }

    /**
     * Handle a failed validation attempt.
     *
     * @param  \Illuminate\Contracts\Validation\Validator  $validator
     * @return void
     *
     * @throws \Illuminate\Validation\ValidationException
     */
    protected function failedValidation(Validator $validator): void
    {
        if ($this->defaultFailedValidationRestored) {
            throw (new ValidationException($validator))
                ->errorBag($this->errorBag)
                ->redirectTo($this->getRedirectUrl());
        }
    }
}

Usage example:

namespace App\Http\Requests;

use Auth;
use Illuminate\Foundation\Http\FormRequest;

class AuthRequest extends FormRequest
{
    use PreventsRedirectWhenFailedTrait;

    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize(): bool
    {
        return Auth::guest();
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules(): array
    {
        return [
            'email' => 'required|email|exists:users',
            'password' => 'required',
            'remember_me' => 'integer',
        ];
    }
}

Inside your controller:

public function authenticate(AuthRequest $request)
    {
        if ($request->validatorPasses()) {
            $data = $request->validated();
            /* your logic */
        } else {
            $errorBag = $request->validatorErrors();
        }

        // or
        if ($request->validatorFails()) {
            // your logic
        }
}

Hope you'll find this helpful.

Creodont answered 17/12, 2019 at 10:32 Comment(1)
Thanks worked like a charm in laravel 8 as wellCloudlet
D
4

I made a middleware (for API requests only) to make the Accept header include application/json by default:

/**
 * Ensures the default Accept header is application/json
 */
class DefaultApiAcceptJson
{
    public function handle(Request $request, \Closure $next)
    {
        $acceptHeader = $request->headers->get('Accept');
        if (!Str::contains($acceptHeader, 'application/json')) {
            $newAcceptHeader = 'application/json';
            if ($acceptHeader) {
                $newAcceptHeader .= "/$acceptHeader";
            }
            $request->headers->set('Accept', $newAcceptHeader);
        }
        return $next($request);
    }
}

This way I always get the validation error JSON response rather than a redirect to the web index page.

Doxia answered 3/9, 2019 at 15:32 Comment(0)
Z
3

I just created a ApiFormRequest who override FormRequest::failedValidation method like this:

<?php
// app/Http/Requests/ApiFormRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;

class ApiFormRequest extends FormRequest
{

    protected function failedValidation(Validator $validator): void
    {
        $jsonResponse = response()->json(['errors' => $validator->errors()], 422);

        throw new HttpResponseException($jsonResponse);
    }
}

Then you simply use like this

<?php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ClientRequest extends ApiFormRequest
{
    // ...
Ziagos answered 31/1, 2019 at 14:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.