Handling error thrown by resource controller's method - laravel

I' working with Laravel 5.6 and i've decided to create a resource controller to handle one of my models. Right know im trying to destroy a record from the database like this:
public function destroy(Role $role)
{
$role->delete();
return response([
'alert' => [
'type' => 'success',
'title' => 'Role destroyed!'
]
], 200);
}
It works just fine as longs as the $role exists. My problem is that i want to handle the response myself in the case that $role does not exist to do something like this:
return response([
'alert' => [
'type' => 'ups!',
'title' => 'There is no role with the provided id!'
]
], 400);
But instead, i'm getting a error like this:
"No query results for model [App\\Models\\Role]."
And that is something I don't want.
Thanks in advance!

The "No query results for model [App\\Models\\Role]." is the standard response message for a ModelNotFound exception in Laravel.
The best way to change the response for an exception like this is to use the exception handler's render function to respond with whatever message you want.
For example you could do
if ($e instanceof ModelNotFoundException) {
$response['type'] = "ups!;
$response['message'] = "Could not find what you're looking for";
$response['status'] = Response::HTTP_NOT_FOUND
}
return response()->json(['alert' => $response], $response['status']);
The alternative is to ensure that the ModelNotFound exception does not get thrown (So use ->find() rather than ->findOrFail() when querying the model)
and then using the abort helper like so if no results are returned:
abort(400, 'Role not found');
or
return response(['alert' => [
'type' => 'ups!',
'title' => 'There is no role with the provided id!']
],400);

Related

How to remove console error 422 (Unprocessable Content)

I write validation for email in my controller
public function store(Request $request)
{
/*
* validate request
*/
$request->validate([
'email' => ['required', 'unique:leads', 'email'],
]);
return response()->json([],422);
if (\App\Models\Lead::where(['email' => $request->get('email')])->count() > 0) {
// user found
return response()->json([ 'data' => [
'message' => 'lindirizzo email è già registrato'
]], 200);
}
else {
// Register the new user or whatever.
$client = \App\Models\Lead::create(['email' => $request->get('email'),]);
return response()->json([ 'data' => [
'message' => 'Registrato con successo!'
]], 201);
}
}
And when i write the same email in front i get error POST http://127.0.0.1:8000/api/register/leads 422 (Unprocessable Content)
My network respons its ok,
{"message":"validation.unique","errors":{"email":["validation.unique"]}}
But i dont want to show error in console
This line of code is unnecessary.
// ...
return response()->json([],422);
// ...
Remove it.
Writing The Validation Logic
... if the validation fails, the proper response will automatically be
generated. If the validation passes, our controller will continue
executing normally.
Addendum
The block of code below is unnecessary as well since the validation rule 'unique:leads' is sufficient:
// ...
if (\App\Models\Lead::where(['email' => $request->get('email')])->count() > 0) {
// user found
return response()->json([ 'data' => [
'message' => 'lindirizzo email è già registrato'
]], 200);
}
// ...
Remove it as well!
If you wish to have a custom message for the "unique email" validation, add it as the second parameter of the ->validate(...) method:
$request->validate([
'email' => ['required', 'unique:leads', 'email'],
], [
'email.unique' => 'lindirizzo email è già registrato'
]);
First, you need to check is there any email is available already or not in the table.
If you found any count of that email then simply ignore creating a record else you can add that email with a new entry inside the database.
if (Lead::where(['email' => $request->get('email')])->count() > 0) {
return response()->json([ 'data' => [
'message' => 'User data get successfully'
]], 200);
}else{
// Add a user with new email and send json response
}
}

Changing default error message when record not found - Laravel API

I am building a simple API, which there is a point that when the ID entered in the endpoint URL does not point to a valid record, I get a standard NotFoundHttpException. And I cannot figure out how to override this in order to provide my own error message response as I do not wish to share my Model name etc.
Endpoint
Route::get('{mrl}', [MrlController::class, 'show']);
Controller
public function show(Mrl $mrl)
{
if ($data = $mrl) {
return response(['status' => 'ok', 'data' => $data], 200);
} else {
return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
}
}
When I run this when a record exists I receive the following which is what I expect.
{
"status": "ok",
"data": {
"id": 98,
"market_id": 1,
"crop_id": 2,
"chemical_id": 113,
"maximum_residue_level": null,
"exempt": 0,
"comments": null,
"date_verified": "2021-10-07",
"created_at": "2021-10-19T05:42:12.000000Z",
"updated_at": "2021-10-19T05:42:12.000000Z"
}
}
However, when I enter an ID in the route endpoint for a record that does not exist, I receive the following:
{
"message": "No query results for model [App\\Seasonal\\Mrl] 99"
}
This is happening from what I understand to be the auto find of the record between the Route and the controller, and I am lost as to how to customize this behavior.
Thanks in advance for your help!
you don't show the code where you are fetchning the model from the database but we can assume something like that:
$mrl = Mrl::findOrFail($id);
show($mrl);
The model findOrFail() method throws an exception when the model is not found, which is convenient when you want to adapt the response.
You can imagine something like that:
try {
$mrl = Mrl::findOrFail($id);
return response(['status' => 'ok', 'data' => $data], 200);
} catch (ModelNotFoundException $e) {
return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
}
The idea is to catch the error thrown by your model to change the message and status code of the response.
When building APIs you should event add a "generic" catch statement for any unhandled errors to display a standardized generic error message and log what happened, like this:
try {
$mrl = Mrl::findOrFail($id);
// Do more things that could generate errors ?
return response(['status' => 'ok', 'data' => $data], 200);
} catch (ModelNotFoundException $e) {
// Not found
return response(['status' => 'error', 'message' => 'Could not retrieve data'], 500);
} catch (\Exception $e) {
// Generic error
\Log::error($e);
return response(['status' => 'error', 'message' => 'An error occured'], 500);
}
After spending time reading the docs I finally found the answer on how to customize the behaviour if a record is found when utilising Eloquents Route Model Binding.
In the Laravel docs, there is a method for this explicit purpose, to override the default behaviour when the bound model does not have a valid record.
The method for doing this is to chain the ->missing() function call to the end of your route and then providing the behaviour for the response you wish to provide. As below:
Route::get('{mrl}', [MrlController::class, 'show'])->missing(function () {
return response(['status' => 'error', 'message' => 'Invalid query.'], 404);
});
Chaining this method onto my request has enabled me to return the generic response I was hoping for when querying an invalid model record.

How to validate inputs from GET request in Laravel

I wanted to validate inputs from a GET request without using the
this->validate($request... or \Validator::make($request...
and prefer to do it like
$input = $request->validate([... rules ...]);
however since get requests doesn't have $request parameters how can I achieve it?
public function sampleGet($param1, $param2) {
// How can I pass the $param1 and $param to to validate?
$input = $request->validate([
'param1' => 'required',
'param2' => 'required
]);
}
You can do so and it will have same behavior as validate
validator($request->route()->parameters(), [
'param1' => 'required',
'param2' => 'required'
....
])->validate();
If you want all the route parameters you can get them as an array:
$request->route()->parameters()
Since you already have those parameters being passed to your method you can just build an array with them:
compact('param1', 'param2');
// or
['param1' => $param1, 'param2' => $param2];
You are not going to be using the validate method on the Request though, you will have to manually create a validator. Unless you want to merge this array into the request or create a new request with these as inputs.
There is nothing special about the validate method on a Controller or on a Request. They are all making a validator and validating the data the same way you would yourself.
When manually creating a validator you still have a validate method that will throw an exception, which would be the equivalent to what is happening on Request and the Controller with their validate methods.
Laravel 7.x Docs - Validation - Manualy Creating Validators - Automatic Redirection
You can do like that.
public function getData(Request $request)
{
try {
$input['route1'] = $request->route('route1');
$input['route2'] = $request->route('route2');
$valid = Validator::make($input, [
'route1' => 'required',
'route2' => 'required'
]);
} catch (\Throwable $th) {
echo "<pre>";print_r($th->__toString());die;
}
}
Or you can follow the below link for more info.
https://laravel.com/docs/7.x/validation#manually-creating-validators

Find data before validate form request laravel

I want to update the data using the request form validation with a unique email role, everything works normally.
Assume I have 3 data from id 1-3 with url:
127.0.0.1:8000/api/user/update/3
Controller:
use App\Http\Requests\Simak\User\Update;
...
public function update(Update $request, $id)
{
try {
// UPDATE DATA
return resp(200, trans('general.message.200'), true);
} catch (\Exception $e) {
// Ambil error
return $e;
}
}
FormRequest "Update":
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->id,
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
but if the updated id is not found eg:
127.0.0.1:8000/api/user/update/4
The response gets The email has already been taken.
What is the solution so that the return of the data is not found instead of validation first?
The code looks like it should work fine, sharing a few things below that may help.
Solution 1: Check if $this->id contains the id you are updating for.
Solution 2: Try using the following changes, try to get the id from the URL segment.
public function rules()
{
return [
'user_akses_id' => 'required|numeric',
'nama' => 'required|max:50',
'email' => 'required|email|unique:users,email,' . $this->segment(4),
'password' => 'required',
'foto' => 'nullable|image|max:1024|mimes:jpg,png,jpeg',
'ip' => 'nullable|ip',
'status' => 'required|boolean'
];
}
Sharing one more thing that may help you.
Some person uses Request keyword at the end of the request name. The Update sounds generic and the same as the method name you are using the request for. You can use UpdateRequest for more code readability.
What I understand from your question is, you need a way to check if the record really exists or not in the form request. If that's the case create a custom rule that will check if the record exists or not and use that rule inside your request.
CheckRecordRule
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class CheckRecordRule implements Rule
{
protected $recordId;
public function __construct($id)
{
$this->recordId = $id;
}
public function passes($attribute, $value)
{
// this will check and return true/false
return User::where('id', $this->recordId)->exists();
}
public function message()
{
return 'Record not found.';
}
}
Update form request
public function rules()
{
return [
'email' => 'required|email|unique:users,email,' . $this->id.'|'. new CheckRecordRule($this->id),
];
}
So when checking for duplicate it will also check if the record really exists or not and then redirect back with the proper message.

Better way for testing validation errors

I'm testing a form where user must introduce some text between let's say 100 and 500 characters.
I use to emulate the user input:
$this->actingAs($user)
->visit('myweb/create')
->type($this->faker->text(1000),'description')
->press('Save')
->see('greater than');
Here I'm looking for the greater than piece of text in the response... It depends on the translation specified for that validation error.
How could do the same test without having to depend on the text of the validation error and do it depending only on the error itself?
Controller:
public function store(Request $request)
{
$success = doStuff($request);
if ($success){
Flash::success('Created');
} else {
Flash::error('Fail');
}
return Redirect::back():
}
dd(Session::all()):
`array:3 [
"_token" => "ONoTlU2w7Ii2Npbr27dH5WSXolw6qpQncavQn72e"
"_sf2_meta" => array:3 [
"u" => 1453141086
"c" => 1453141086
"l" => "0"
]
"flash" => array:2 [
"old" => []
"new" => []
]
]
you can do it like so -
$this->assertSessionHas('flash_notification.level', 'danger'); if you are looking for a particular error or success key.
or use
$this->assertSessionHasErrors();
I think there is more clear way to get an exact error message from session.
/** #var ViewErrorBag $errors */
$errors = request()->session()->get('errors');
/** #var array $messages */
$messages = $errors->getBag('default')->getMessages();
$emailErrorMessage = array_shift($messages['email']);
$this->assertEquals('Already in use', $emailErrorMessage);
Pre-requirements: code was tested on Laravel Framework 5.5.14
get the MessageBag object from from session erros and get all the validation error names using $errors->get('name')
$errors = session('errors');
$this->assertSessionHasErrors();
$this->assertEquals($errors->get('name')[0],"The title field is required.");
This works for Laravel 5 +
Your test doesn't have a post call. Here is an example using Jeffery Way's flash package
Controller:
public function store(Request $request, Post $post)
{
$post->fill($request->all());
$post->user_id = $request->user()->id;
$created = false;
try {
$created = $post->save();
} catch (ValidationException $e) {
flash()->error($e->getErrors()->all());
}
if ($created) {
flash()->success('New post has been created.');
}
return back();
}
Test:
public function testStoreSuccess()
{
$data = [
'title' => 'A dog is fit',
'status' => 'active',
'excerpt' => 'Farm dog',
'content' => 'blah blah blah',
];
$this->call('POST', 'post', $data);
$this->assertTrue(Post::where($data)->exists());
$this->assertResponseStatus(302);
$this->assertSessionHas('flash_notification.level', 'success');
$this->assertSessionHas('flash_notification.message', 'New post has been created.');
}
try to split your tests into units, say if you testing a controller function
you may catch valication exception, like so:
} catch (ValidationException $ex) {
if it was generated manually, this is how it should be generated:
throw ValidationException::withMessages([
'abc' => ['my message'],
])->status(400);
you can assert it liks so
$this->assertSame('my message', $ex->errors()['abc'][0]);
if you cannot catch it, but prefer testing routs like so:
$response = $this->json('POST', route('user-post'), [
'name' => $faker->name,
'email' => $faker->email,
]);
then you use $response to assert that the validation has happened, like so
$this->assertSame($response->errors->{'name'}[0], 'The name field is required.');
PS
in the example I used
$faker = \Faker\Factory::create();
ValidationException is used liks this
use Illuminate\Validation\ValidationException;
just remind you that you don't have to generate exceptions manually, use validate method for common cases:
$request->validate(['name' => [
'required',
],
]);
my current laravel version is 5.7

Resources