Skip to content

Commit

Permalink
Unify validation exception formatting. (#20173)
Browse files Browse the repository at this point in the history
* Unify validation exception formatting.

This unifies all validation error formatting into the exception handler
and out of Form Request. “Don’t Flash” is typically a global concept
that always applies to a list of fields for all pages in the
applications, so seemed to make sense to specify in one place instead
of each form.

Total control of the Form Request error response can still be gained by
overriding failedValidation and throwing a HttpResponseException with
whatever response you want.

* Apply fixes from StyleCI (#20174)

* shorten method call

* remove old test

* Apply fixes from StyleCI (#20175)
  • Loading branch information
taylorotwell authored Jul 19, 2017
1 parent 98b56b7 commit e73fed3
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 82 deletions.
31 changes: 21 additions & 10 deletions src/Illuminate/Foundation/Exceptions/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Handler implements ExceptionHandlerContract
protected $container;

/**
* A list of the exception types that should not be reported.
* A list of the exception types that are not reported.
*
* @var array
*/
Expand All @@ -62,6 +62,16 @@ class Handler implements ExceptionHandlerContract
\Illuminate\Validation\ValidationException::class,
];

/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];

/**
* Create a new exception handler instance.
*
Expand Down Expand Up @@ -222,27 +232,28 @@ protected function convertValidationExceptionToResponse(ValidationException $e,
*/
protected function invalid($request, ValidationException $exception)
{
return redirect()->back()->withInput($request->except([
'password',
'password_confirmation',
]))->withErrors(
$exception->validator->errors()->messages(),
$exception->errorBag
);
$url = $exception->redirectTo ?? url()->previous();

return redirect($url)
->withInput($request->except($this->dontFlash))
->withErrors(
$exception->errors(),
$exception->errorBag
);
}

/**
* Convert a validation exception into a JSON response.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Validation\ValidationException $exception
* @return \Illuminate\Http\Response
* @return \Illuminate\Http\JsonResponse
*/
protected function invalidJson($request, ValidationException $exception)
{
return response()->json([
'message' => $exception->getMessage(),
'errors' => $exception->validator->errors()->messages(),
'errors' => $exception->errors(),
], 422);
}

Expand Down
56 changes: 3 additions & 53 deletions src/Illuminate/Foundation/Http/FormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Illuminate\Foundation\Http;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Routing\Redirector;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Validation\Validator;
Expand Down Expand Up @@ -59,13 +58,6 @@ class FormRequest extends Request implements ValidatesWhenResolved
*/
protected $errorBag = 'default';

/**
* The input keys that should not be flashed on redirect.
*
* @var array
*/
protected $dontFlash = ['password', 'password_confirmation'];

/**
* Get the validator instance for the request.
*
Expand Down Expand Up @@ -122,51 +114,9 @@ protected function validationData()
*/
protected function failedValidation(Validator $validator)
{
throw new ValidationException($validator, $this->response(
$this->formatErrors($validator)
));
}

/**
* Get the proper failed validation response for the request.
*
* @param array $errors
* @return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->expectsJson()) {
return $this->jsonResponse($errors);
}

return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}

/**
* Get the proper failed validation JSON response for the request.
*
* @param array $errors
* @return \Illuminate\Http\JsonResponse
*/
public function jsonResponse(array $errors)
{
return new JsonResponse([
'message' => 'The given data was invalid.',
'errors' => $errors,
], 422);
}

/**
* Format the errors from the given Validator instance.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return array
*/
protected function formatErrors(Validator $validator)
{
return $validator->getMessageBag()->toArray();
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}

/**
Expand Down
43 changes: 43 additions & 0 deletions src/Illuminate/Validation/ValidationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class ValidationException extends Exception
*/
public $errorBag;

/**
* The path the client should be redirected to.
*
* @var string
*/
public $redirectTo;

/**
* Create a new exception instance.
*
Expand All @@ -44,6 +51,42 @@ public function __construct($validator, $response = null, $errorBag = 'default')
$this->validator = $validator;
}

/**
* Get all of the validation error messages.
*
* @return array
*/
public function errors()
{
return $this->validator->errors()->messages();
}

/**
* Set the error bag on the exception.
*
* @param string $errorBag
* @return $this
*/
public function errorBag($errorBag)
{
$this->errorBag = $errorBag;

return $this;
}

/**
* Set the URL to redirect to on a validation error.
*
* @param string $url
* @return string
*/
public function redirectTo($url)
{
$this->redirectTo = $url;

return $this;
}

/**
* Get the underlying response instance.
*
Expand Down
19 changes: 0 additions & 19 deletions tests/Foundation/FoundationFormRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Illuminate\Routing\UrlGenerator;
use Illuminate\Http\RedirectResponse;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\ValidationException;
use Illuminate\Contracts\Translation\Translator;
use Illuminate\Validation\Factory as ValidationFactory;
use Illuminate\Contracts\Validation\Factory as ValidationFactoryContract;
Expand Down Expand Up @@ -56,24 +55,6 @@ public function test_validate_method_throws_when_authorization_fails()
$this->createRequest([], FoundationTestFormRequestForbiddenStub::class)->validate();
}

public function test_redirect_response_is_properly_created_with_given_errors()
{
$request = $this->createRequest();

$this->mocks['redirect']->shouldReceive('withInput')->andReturnSelf();

$this->mocks['redirect']
->shouldReceive('withErrors')
->with(['name' => ['error']], 'default')
->andReturnSelf();

$e = $this->catchException(ValidationException::class, function () use ($request) {
$request->validate();
});

$this->assertInstanceOf(RedirectResponse::class, $e->getResponse());
}

public function test_prepare_for_validation_runs_before_validation()
{
$this->createRequest([], FoundationTestFormRequestHooks::class)->validate();
Expand Down

0 comments on commit e73fed3

Please sign in to comment.