I ran into the same problem and went with the old style Validator in my API methods.
L5: API Validation and the new FormRequest approach
Following on from the Laracast: https://laracasts.com/series/incremental-api-development/episodes/7
Does anyone have a nice approach which couples the new L5 FormRequest injection with the nice layout for triggering error responses via the base api controller?
At the moment I'm thinking I will need to abandon FormRequests and just use old style Validator::make on my API store methods if I want to make use of the base API responses.
Any thought appreciated!
What do you mean by "the nice layout for triggering error responses via the base api controller?"
@pmall If you watch the incremental API development series, you'll see what I mean.
Basically all responses are extracted to a base ApiController which each ApiModelController extends, so you can do stuff like
if($validation->fails()){
return $this->setStatusCode(422)
->respondWithError('Basic validation failed');
}
But if we intercept validation with FormRequest injection, then the responses aren't available as it's captured before we hit the controller method.
Hum ok I didn't remembered because FormRequest do returns a 422 response with json errors but it is in case of an ajax request :
// vendor/laravel/src/Illuminate/Foundation/Http/FormRequest.php
public function response(array $errors)
{
if ($this->ajax())
{
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors);
}
It may work with wantsJson() instead of ajax().
you can override the response method and return your custom response. laravel now has a Request abstract class that extends FormRequest, you can put your overridden method there since the Request class is the base class of all your requests.
Thanks for the answers guys. Much appreciated. I'll have another go at it on Monday :)
Anyway I really think FormRequest should use wantsJson() instead ajax() by default.
+1 to @arabsight. I moved the base controller functions to a trait and am overriding the response function.
use ApiResponse;
public function response(array $errors)
{
return $this->respondBadRequest(array_flatten($errors));
}
I took Socks approach and did one more thing. Since I am using the same controller method for both API and application form input I added one more thing.
In the application form I added a hidden Form input which can be anything. But then in the response I can check if that Input field actually exists in the request. If it does I know it is from a Form and can treat it normally. If that field isn't there then I know it is an API request and can handle that response appropriately.
I handle it in my response in my trait if it from API and if not just pass it along to be handled normally.
@mhess your API is expecting json data right ? Why not just use $request->wantsJson() ?
Ooooo. I like. Thanks. I hadn't thought of that detail to narrow down the requesting origin. I wasn't crazy about the hidden field but nothing else popped in my head.
@socks Great solution!
With Laravel 5.3 I did following in app/Http/Requests/PostRequest.php
/*
* Validator instance updated on failedValidation
*
* @var \Illuminate\Contracts\Validation\Validator
*/
public $validator = null;
/**
* Overrid Handle a failed validation attempt.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
{
// throw new \Illuminate\Validation\ValidationException($validator, $this->response(
// $this->formatErrors($validator)
// ));
$this->validator = $validator;
}
In my Controller function, I captured the $validation object
public function create(PostRequest $request)
{
if (isset($request->validator) && $request->validator->fails()) {
throw new \Exception('GEN-INVALID-PARAMS - ' . $request->validator->messages()->first(), 999);
}
...
Hope this helps
Please or to participate in this conversation.