Custom error code for laravel form request - php

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.

Related

Laravel: make validation rule fail when using closure

Say I have a custom logic for validating the uniqueness of a FormRequest field, something requiring to find another resource in the database like below:
class CreateMyResourceRequest extends FormRequest {
public function rules() {
return [
'my_field' => [
Rule::unique('some_other_resource', 'some_column')
->where(function ($query) {
$otherResource = SomeOtherResource::where(...)->firstOrFail();
// Process the retrieved resource
}),
]
The firstOrFail() call obviously makes the request fail with a 404 - Not found while I would like to return a 422 with a validation error on the field.
Is there a way to achieve this while still using the Rule::unique() provided by the framework?
Thanks in advance!
I'd suggest the following
public function rules()
{
return [
"your_field" => ["you_can_have_more_validations_here", function($key, $value, $cb) {
$queryResult = SomeModel::find(1);
if (someCondition) {
$cb("your fail message");
}
}]
];
}
when the $cb run the validation will fail with 422
Don't use firstOrFail, instead just use first and check if the output is null. If it is, return false.
Alternatively, the more Laravel way of doing it is:
$query->leftJoin('otherresource', 'primarykey', 'foreignkey')->where(...)

How to show validation errors in laravel api while having a form request file separate from controller

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.

Checking if there is any validation error when using custom form request class

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));
}

Laravel Passport Route redirects to login page

I'm using Laravel 5.3 & Passport.
When using Postman to test any route I have set in api.php file it always returns the login page. Here is an example of my testing route:
Route::get('/getKey', function() {
return 'hello';
})->middleware('client_credentials');
Postman params:
Accept application/json
Authorization Bearer <then my key>
I have set middleware to 'auth:api' per another solution I found while searching for the answer.
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware('auth:api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
I've tried just about every solution that has worked for others but still no luck. Any suggestions would be much appreciated.
UPDATE
So I finally got something to work. I created a consumer app and created a few test functions. I was able to consume the api, with verification of token. However, hitting this Route no longer returns my login page, but instead now returns nothing. So its still not working for whatever reason.
Route::get('/user', function (Request $request) {
return $request->user();
})->middleware('client_credentials');
The redirection to the defined login route is occurring in the app\Exceptions\Handler.php class.
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->expectsJson()) {
return response()->json(['error' => 'Unauthenticated.'], 401);
}
return redirect()->guest(route('login'));
}
The function tries to detect whether it is being called from an API (it which case it returns a 401 Unauthorized reponse with JSON message) and if not it will redirect to the login page according to the comments it
Converts an authentication exception into an unauthenticated response
To resolve the issue in postman, on the request click on the Headers tab and add:
key: Accept
value: application/json
I'm pretty new to this so am not sure whether this is a header we should be adding when testing all API calls with Postman or just a nusience with how this laravel method is setup.
Anyway this would solve your issue with being redirected to the login page, however it's a sign your underlying authentication isn't working
You need to add Authorization: Bearer YOUR_TOKEN to your every request's header. Also, for latest version Laravel 5.5 or above. You need to add Accept: application/json to request header too.
Add this code on Headers on postman.
key Value
Accept application/json
It is coded to check whether the request comes from Ajax. In that case you will receive the following json if authentication fails:
{
"error": "Unauthenticated."
}
Otherwise it will assume you are using a browser and it will redirect to Html login page for authentication.
You can add the following header to your request to simulate an Ajax request:
X-Requested-With = XMLHttpRequest
From laravel 5.8 till the current 6.0 version there is a a middleware located at the app/http/Middleware which is \App\Http\Middleware\Authenticate, it has a method
redirectTo
with the code
protected function redirectTo($request)
{
if (! $request->expectsJson()) {
return route('login');
}
}
Re-write this to
protected function redirectTo($request)
{
if (! ($request->expectsJson() || collect($request->route()->middleware())->contains('api'))) {
return route('login');
}
}
What this code does is to return a Illuminate\Routing\Redirector instance and sets it as the \Illuminate\Auth\AuthenticationException $redirectTo parameter . which is passed to \App\Exceptions#render by laravel.
At the render function you can write a logic to catch an \Illuminate\Auth\AuthenticationException exception.
Here is my own implementation
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
/**
* Render an Authentification exception when user trying to viditing a route or
* Perform an action is not properly authenticated
*/
if ($exception instanceof \Illuminate\Auth\AuthenticationException) {
return $this->unauthenticated($request,$exception);
}
}
/**
* Convert an authentication exception into a response.
*
* #param \Illuminate\Http\Request $request
* #param \Illuminate\Auth\AuthenticationException $exception
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function unauthenticated($request, \Illuminate\Auth\AuthenticationException $exception)
{
return $exception->redirectTo()
? redirect()->guest($exception->redirectTo())
: response()->json(['message' => $exception->getMessage()], 401);
}
As noted in many of the answers the reason this happens is indeed the code:
if (! $request->expectsJson()) {
return route('login');
}
in app\Http\Middleware\Authenticate.php
One way to solve this is to wrap your api requests in Middleware that adds 'Accept: application/json' to the header of those requests.
I got this idea from this article: https://medium.com/#martin.riedweg/laravel-5-7-api-authentification-with-laravel-passport-92b909e12528
Laravel version: 9.*
// app/Http/Middleware/Authenticate.php
protected function redirectTo($request)
{
return '/api/unauthenticated'; // You can name whatever route you want
}
// routes/api.php
Route::get('/unauthenticated', function () {
return response()->json(['message' => 'Unauthenticated.'], 403);
});
Hope this helps.
Happy coding!
If you don't want to enforce your api consumer to pass Accept - application/json in all the request headers you can add the following method in App\Exceptions\Handlers.php; to customize error response
//use Illuminate\Auth\AuthenticationException;
protected function unauthenticated($request, AuthenticationException $exception)
{
if ($request->is('api/*')) {
return response()->json(['error' => 'Invalid token', 'status' => false], 401);
}
return redirect()->guest(route('login'));
}
if you are using username instead of email for credential; insert this method at your User model:
function findForPassport($username) {
return $this->where('username', $username)->first();
}

Consistent REST API Response in Laravel+Dingo

I have been developing a set of rest APIs to be exposed for mobile apps. I am following the repository pattern for the development on the Laravel project. How do I implement a presenter and transformer for formatting a constant JSON output throughout the set of all my APIs?
For example I have the following controller for login
public function authenticate()
{
$request = Request::all();
try {
// If authenticated, issue JWT token
//Showing a dummy response
return $token;
} catch (ValidatorException $e) {
return Response::json([
'error' =>true,
'message' =>$e->getMessageBag()
]);
}
}
Now where does a transformer and presenter come into the picture? I know that both are used to format the output by converting the db object and produce a formatted JSON so that it remains uniform across my APIs.
The dingo API and fractal or even the framework (L5 repository) don't provide detailed documentation and I can't find any tutorials on this.
I have created the following presenter and transformer for another API which gives the list of products
namespace App\Api\V1\Transformers;
use App\Entities\Product;
use League\Fractal\TransformerAbstract;
class UserTransformer extends TransformerAbstract {
public function transform(\Product $product)
{
return [
'id' => (int) $product->products_id
];
}
}
Presenter
<?php
namespace App\Api\V1\Presenters;
use App\Api\V1\Transformers\ProductTransformer;
use Prettus\Repository\Presenter\FractalPresenter;
/**
* Class ProductPresenter
*
* #package namespace App\Presenters;
*/
class ProductPresenter extends FractalPresenter
{
/**
* Transformer
*
* #return \League\Fractal\TransformerAbstract
*/
public function getTransformer()
{
return new UserTransformer();
}
}
How will I set the presenter in the controller and respond back? Tried
$this->repository->setPresenter("App\\Presenter\\PostPresenter");
But it doesn't seems to work and the doc doesn't shows the complete steps.
In the above example, how can I make a template for an error response which I can use throughout my APIs and how will I pass my error exceptions to it?
It seems like presenter and transformer can be used to convert database objects into presentable JSON and not anything else. Is that right?
How do you use a presenter and a transformer for a success response and an error response? By passing exceptions, instead of DB objects to the transformer?
I had the same exact problem and here is how I used dingo with transformer
Controller:
public function update(Request $request)
{
$bus = new CommandBus([
$this->commandHandlerMiddleware
]);
$agency = $bus->handle(
new UpdateAgencyCommand($request->user()->getId(), $request->route('id'), $request->only('name'))
);
return $this->response->item($agency, new AgencyTransformer());
}
Transformer:
class AgencyTransformer extends TransformerAbstract
{
public function transform(AgencyEntity $agencyEntity)
{
return [
'id' => (int) $agencyEntity->getId(),
'name' => $agencyEntity->getName(),
];
}
}
and this is how I handle errors:
throw new UpdateResourceFailedException('Could not update agency.', $this->agencyUpdateValidator->errors());
I just now see your similar question here as well. So see my answer on your other question here: https://stackoverflow.com/a/34430595/429719.
From the other question I derived you're using Dingo, so use that as a structured response class. Make sure you're controller extends from Dingo and then you can just return items and collections in a structured way like:
return $this->response->item($user, new UserTransformer);
return $this->response->collection($users, new UserTransformer);
If you want a nice error handling look for the docs here: https://github.com/dingo/api/wiki/Errors-And-Error-Responses
Basically you can throw any of the core exceptions or a few custom Dingo ones. The Dingo layer will catch them and returns a structured JSON response.
As per the Dingo docs:
throw new Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException('Nope, no entry today!');
Will generate:
{
"message": "Nope, no entry today!",
"status_code": 403
}
You can try use,[Midresapi]: https://github.com/oktorino/midresapi. this will return consistens success or failled response, work in laravel 7 & 8 , Handling validation response, handling 500 response:
$users=\App\User::latest()->limit(2)->get();
return response($users);
#or
return fractal()
->collection($users)
->transformWith(new \App\Transformers\UserTransformer)
->toArray();
Response :
{
"status_code": 200,
"success": true,
"message": "ok",
"data": [
{
"username": "dany",
"email": "jancuk#sabarbanget.com"
},
{
"username": "scc-client-5150",
"email": "dancuk#gmail.com"
}
]
}
Fractal is fully documented here: http://fractal.thephpleague.com/
There is an excellent book that I regularly read from the Phil Sturgeon https://leanpub.com/build-apis-you-wont-hate You can find most of the books code available in github https://github.com/philsturgeon/build-apis-you-wont-hate. You can find really nice examples of Fractal in there.
I would create an Api Controller and extend it from my Controllers.In there there should be all the respond functions (respondWithError, respondWithArray etc.)
Tranformers are transforming objects in a consistent json format so that all your endpoints return the same thing for each entity.
Dont really have an answer on this
There are enough examples in Fractal documentation.

Categories