I'm creating validation (via FormRequest ) for my API and I need to change status code depending on failed validation rule (e.g. If id is string instead of int, get 400. If id doesn't exists, get 404).
I wanted to write something like that:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
$failedRules = $this->getValidatorInstance()->failed();
$statusCode = 400;
if (isset($failedRules['id']['Exists'])) $statusCode = 404;
return response($errors, $statusCode);
}
However, $this->getValidatorInstance()->failed() returns empty array
Why does $this->getValidatorInstance()->failed return empty array?
How can I fix that? Is there some other way to return status code depending on failed validation rule?
The reason you're getting an empty array when your call $this->getValidatorInstance()->failed() is because it's actually resolving a new instance of Validator.
What you can do is call passes() on the new Validator instance which will then allow you to call failed() to get the rules:
$validator = $this->getValidatorInstance();
$validator->passes();
$failedRules = $validator->failed();
Alternatively, if you don't want to have the validator run twice you could override the failedValidation method to store the Validation instance in class:
protected $currentValidator;
protected function failedValidation(Validator $validator)
{
$this->currentValidator = $validator;
throw new ValidationException($validator, $this->response(
$this->formatErrors($validator)
));
}
public function response(array $errors)
{
$failedRules = $this->currentValidator->failed();
$statusCode = 400;
if (isset($failedRules['id']['Exists'])) $statusCode = 404;
return response($errors, $statusCode);
}
Hope this helps!
Related
I have an endpoint I would like to post a bulk update request like this:
{
"resources": [
{
"id": 5,
"name": "ABC"
}
]
}
I am trying to prevent someone updating a resource they are not the owner of. I can create the following rule to prevent this:
'resources.*.id' => ['required', 'exists:resources,id,team_id,' . $this->team()->id],
I'd like to customize the error code so that I receive a 403 error code when this rule is violated. All other rules I am happy with the normal 422 error code.
I am aware I can customize the message in the messages() method. Is there something similar so I can return my own error code? At the moment I just get the standard 422 code.
I am also aware I could load all the team resources in the authorize() method, but I wonder if there is a better way?
Thanks.
Maybe I'm a bit late, but hopefully, my answer can still help you or someone in the future.
You can see the Form Request Validation Class you created extends from Illuminate\Foundation\Http\FormRequest. Then if you look at the source code on the Laravel Framework, there is a function to handle failed validation attempts.
So the solution, you can override the default failedValidation function in the Form Request Validation Class you created. Here is a sample code you can use:
// ./app/Http/Requests/UpdateFooBarRequest.php
...
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Response;
use Illuminate\Validation\ValidationException;
class UpdateFooBarRequest extends FormRequest
{
...
/**
* Handle a failed validation attempt.
*
* #param \Illuminate\Contracts\Validation\Validator $validator
* #return void
*
* #throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl())
->status(Response::HTTP_FORBIDDEN);
}
}
Credit: Discussion from Laracasts - How to make a custom FormRequest error response in Laravel 5.5
If you are extending FormRequest, then you can add your custom messages in the messages function,
public function messages() {
$messages = [];
foreach ($this->get('resources') as $key => $val) {
$messages["resources.$key.id.exists"] = "The data :attribute"; // will send 'The data exists'
$messages["resources.$key.id.required"] = "The data is :attribute";
}
return $messages;
}
#Edit: 2nd Part of Question
If the authorize method returns false, an HTTP response with a 403 status code will automatically be returned and your controller method will not execute.
Source
public function authorize()
{
return false;
}
Then it will send 403 requests instead of 422.
I'm working with Symfony 4.4 / API Platform, and I'm trying to return the response from the DataPersister or to set up its code.
In my DataPersister, I test if the Admin->isManager() is true, So Admin can never be deleted, So in this case I want to return a custom status code in my response 414, and a message "thisAdminIsManager"
AdminDataPersister:
final class AdminDataPersister implements ContextAwareDataPersisterInterface
{
/* #var EntityManagerInterface */
private $manager;
public function __construct(
EntityManagerInterface $manager
){
$this->manager = $manager;
}
public function supports($data, array $context = []): bool
{
return $data instanceof Admin;
}
public function persist($data, array $context = [])
{
$this->manager->persist($data);
$this->manager->flush();
}
public function remove($data, array $context = [])
{
/* #var Admin $data */
#The Manager can never be deleted:
if( $data->getManager() ){
return; //here I want to return the custom response
}
$this->manager->remove($data);
$this->manager->flush();
}
You should throw an exception, then you should configurate your api_platform to handle this exception. ApiPlatform will convert exception into a response with message and the specified code.
Step1: Create dedicated exception class
<?php
// api/src/Exception/ProductNotFoundException.php
namespace App\Exception;
final class AdminNonDeletableException extends \Exception
{
}
Step2: In your data persister, throw exception:
public function remove($data, array $context = [])
{
/* #var Admin $data */
#The Manager can never be deleted:
if( $data->getManager() ){
throw new AdminNonDeletableException('thisAdminIsManager');
}
$this->manager->remove($data);
$this->manager->flush();
}
Step3: Add your exception in the config/package/api_platform.yaml file and declare the code number (414)
# config/packages/api_platform.yaml
api_platform:
# ...
exception_to_status:
# The 4 following handlers are registered by default, keep those lines to prevent unexpected side effects
Symfony\Component\Serializer\Exception\ExceptionInterface: 400 # Use a raw status code (recommended)
ApiPlatform\Core\Exception\InvalidArgumentException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_BAD_REQUEST
ApiPlatform\Core\Exception\FilterValidationException: 400
Doctrine\ORM\OptimisticLockException: 409
# Custom mapping
App\Exception\AdminNonDeletableException: 414 # Here is the handler for your custom exception associated to the 414 code
You can find more information in error handling chapter
I have a form request file handling my validations separate from my controller. How do i return validation errors after an api call within the controller?
//my controller
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function orders(GetOrdersRequest $request, OrderRepository $orderRepository)
{
$order = $orderRepository->allOrders($request->paginate);
return $this->sendSuccess('Orders retrieved successfully', $order);
}
In the FormRequest class there is a function called failedValidation:
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
It triggers when your validation fails. For API endpoints, this request is a bad response because it is a redirect and contains way too much information. To return a clean and
lightweight json respond, simply oerwrite the function failedValidation in your FormRequest for a matching response for your API. For example like this:
protected function failedValidation(Validator $validator)
{
$errors = $validator->errors();
$response = response()->json([
'message' => 'Invalid data send',
'details' => $errors->messages(),
], 422);
throw new HttpResponseException($response);
}
Credit to https://stackoverflow.com/a/56726819/2311074
Laravel request class returns back automatically when validation fails.
You should show your error messages in view(blade) file.
You can follow official documentation.
For API's it returns automatically a JSON response including error messages.
Basically you can do it in blade file:
#if($errors->has('email'))
<span class="error">{{ $errors->get('email') }}</span>
#endif
You will not be able to fetch the errors in that way since the FormRequest will throw an exception before the request reaches your controller in the event that there is a validation error. However, you can catch the error in the form request it self and modify the response there using the failedValidation method
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
class OrderRequest extends FormRequest
{
public function authorize()
{
//
}
public function rules()
{
//
}
public function failedValidation(Validator $validator)
{
$errors = $validator->errors(); // Here is your array of errors
throw new HttpResponseException($errors);
}
}
I think that's not possible, but you can use the prepareForValidation() method within you FormRequest and manipulate data before validate.
Even when using form request, Laravel handles validation for you automatically without you doing anything. On failed validation, an errors will be returned that you can access in your request response. response.data.errors (depending on the library you use for requests, of course) will contain the errors. For example:
errors: {
name: ["The name must be at least 2 characters"]
}
This is just a dummy example.
Do a quick test by forcing the request to fail and console log the response to see how it will show.
I am using Laravel 5.4. I have a custom form request class where I have my validation rules and messages and I use it in my controller like following :
public function store(CustomFormRequest $request)
{
//
}
I am using ajax to send the request and when there is any validation error, Laravel throws an error with an HTTP response with a 422 status code including a JSON representation of the validation errors.
But I don't want that. Instead, inside my controller's method, I want to find out if there is any validation error and if there is any then I want to return a response with some additional data along with the validation messages, like this:
// Inside my Controller
public function store(CustomFormRequest $request)
{
if ($validator->fails())
{
$errors = $validator->errors();
return response()->json(array('status' => 2, 'msg' => $errors->all() ));
}
}
Could you please help ?
Thanks in advance.
The easiest way to do this would be to override the response() method for the Form Request class.
To do this you can simply add something like the following to your class:
public function response(array $errors)
{
if ($this->expectsJson()) {
return new JsonResponse(['status' => 2, 'msg' => $errors], 422);
}
return parent::response($errors);
}
Don't for get to import Illuminate\Http\JsonResponse
Hope this helps!
I know you want the logic in your controller, but you can still leverage your Request file for this. In the Laravel documentation (assuming you are using the latest version) it is described as Adding After Hooks To Form Requests:
If you would like to add an "after" hook to a form request, you may use the withValidator method. This method receives the fully constructed validator, allowing you to call any of its methods before the validation rules are actually evaluated:
/**
* Configure the validator instance.
*
* #param \Illuminate\Validation\Validator $validator
* #return void
*/
public function withValidator($validator)
{
$validator->after(function ($validator) {
if ($this->somethingElseIsInvalid()) {
$validator->errors()->add('field', 'Something is wrong with this field!');
}
});
}
try this:
form.ajaxSubmit({
async: false,
type: yourMethod,
url: yourRoute,
headers: { 'X-CSRF-TOKEN': "{{csrf_token()}}" },
dataType: 'json',
success: function(data){
location.href = data.redirect_to;
},
error: function(data) {
var errors = data.responseJSON;
var errorsArr = [];
for (error in errors) {
errorsArr.push(errors[error]);
}
errorsArr = $.map(errorsArr, function(n){
return n;
});
alert("<strong class='text-danger'>" + errorsArr.join("<br>") + "</strong>");
console.log(errors);
}
});
and in your controller store method make return:
return response()->json(['redirect_to' => '/your_route']);
After the update of laravel documentation, you don't need to override the response() anymore, all you have to do is just write your business logic inside the protected failedValidation() inside your custom FormRequest class like follows:
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Contracts\Validation\Validator;
/**
* [failedValidation [Overriding the event validator for custom error response]]
* #param Validator $validator [description]
* #return [object][object of various validation errors]
*/
public function failedValidation(Validator $validator) {
// write your business logic here otherwise it will give same old JSON response
throw new HttpResponseException(response()->json($validator->errors(), 422));
}
From following this example I have managed to set up the below Listener/Before Filter to parse all requests to my API endpoint (ie. /api/v1) before any controller actions are processed. This is used to validate the request type and to throw an error if certain conditions are not met.
I would like to differentiate the error response based on the applications environment state. If we are in development or testing, I simply want to throw the error encountered. Alternatively, if in production mode I want to return the error as a JSON response. Is this possible? If not, how could I go about something similar?
I'm using Symfony v3.1.*, if that makes any difference.
namespace AppBundle\EventListener;
use AppBundle\Interfaces\ApiAuthenticatedController;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class ApiBeforeListener
{
/**
* #var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Validates a request for API resources before serving content
*
* #param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
* #return mixed
*/
public function onKernelController(FilterControllerEvent $event)
{
$controller = $event->getController();
if (!is_array($controller))
return;
if ($controller[0] instanceof ApiAuthenticatedController) {
$headers = $event->getRequest()->headers->all();
// only accept ajax requests
if(!isset($headers['x-requested-with'][0]) || strtolower($headers['x-requested-with'][0]) != 'xmlhttprequest') {
$error = new AccessDeniedHttpException('Unsupported request type.');
if (in_array($this->container->getParameter("kernel.environment"), ['dev', 'test'], true)) {
throw $error;
} else {
// return json response here for production environment
//
// For example:
//
// header('Content-Type: application/json');
//
// return json_encode([
// 'code' => $error->getCode(),
// 'status' => 'error',
// 'message' => $error->getMessage()
// ]);
}
}
}
}
}
Unlike most events, the FilterControllerEvent does not allow you to return a response object. Be nice if it did but oh well.
You have two basic choices.
The best one is to simply throw an exception and then add an exception listener. The exception listener can then return a JsonResponse based on the environment.
Another possibility to to create a controller which only returns your JsonResponse then use $event->setController($jsonErrorController) to point to it.
But I think throwing an exception is probably your best bet.
More details here: http://symfony.com/doc/current/reference/events.html