I am trying to follow best practice for REST api on CRUD.
GET: users/ -> all
GET: users/:id -> one specific user.
POST: users/ -> add one
PUT: users/:id -> update specific user.
DELETE: users/:id -> delete one user.
On laravel 8 I want to validate the url :id using the validator, so I have like this on delete user:
$validator = Validator::make(['id' => $request->id], [
'id' => 'exists:users,id,deleted_at,NULL',
]);
And this way to update a user:
$validator = Validator::make(array_merge($request->all(), ['id' => $request->id]), [
'id' => 'required|exists:users,id,deleted_at,NULL',
'name' => 'required',
'surname' => 'required',
'email' => 'required|email:rfc,dns'
]);
As you can see I have to put the id on an array and/or merge with the $request->all().
There is any way in laravel to do this with the request?
I have found 3 ways by Laravel:
$request->add(['variable' => 'value']);
$request->merge(["key"=>"value"]);
$request->request->set(key, value);
But a solution for adding route params to the request before hitting the controller method would be even great.
You can update the request object on the fly and add the ID field, before you validate it, something like
$request['id'] = $id;
// note: the $id is your function's parameter name
$validator = Validator::make(array_merge($request->all()), [
'id' => 'required|exists:users,id,deleted_at,NULL',
'name' => 'required',
'surname' => 'required',
'email' => 'required|email:rfc,dns'
]);
You can do it like you are doing, but doing it with route model binding would be the way to go.
Now when you want to update a user by sending a PUT to /users/:id, and the user does not exist you will get a 422. But what you really want would be a 404.
With route model binding, Laravel will check if the model exists for you and abort with a 404 when it does not.
If route model binding is not an option, you can also just not validate the id with the validator and retrieve the user with User::findOrFail($request->input('id')), the framework will then still abort with a 404 if it can't be found.
Related
I wrote test:
public function test_hello_world(){
$test = User::create(['name' => 'Test',
'email' => 'test#test.com',
'password' => 'password',
]);
Profile::create([
'user_id' => $test->id,
'name' => 'Test',
'slug' => 'test'
]);
$this->get('/profile/test')
->assertStatus(200);
}
What this code should testing? After get to this url it should display details about profile. If profile with this slug doesn't exist, we have error 404. In this test I create user and profile table (this 2 tables is connection) but after run test I have the error:
Expected response status code [200] but received 404. Failed
asserting that 200 is identical to 404.
Why I have this error since I created profile with this slug? For me the best option will be create testing database with a lot of records and conduct all test on it. Is it possible? How do that?
#Edit
I have a route and controller's method which display user's profile. If I go (in web browser) to localhost/profile/slug, it works, if this profile exist. My controller's method look like this:
public function show($slug) {
$profile = Profile::where('slug', $slug)
->firstOrFail();
return Inertia::render('Profile/Single', [
'profile' => $profile,
]);
}
And route:
Route::get('/profile/{slug}',
[ProfileController::class, 'show'])
->name('profile.show');
According to your requirement you have to create route for getting profile from slug name. You did wrong in single function. Without creating route it will not worked.
So below example may work for you.
For example:-
For creating data
public function createData(){
$user = User::create(['name' => 'Test',
'email' => 'test#test.com',
'password' => 'password',
]);
Profile::create([
'user_id' => $user->id,
'name' => 'Test',
'slug' => 'test'
]);
return redirect()->back()->with('success', 'Data created');
}
For Getting Data
public function getDataBySlug($slug){
$profile = Profile::where('slug',$slug)->firstOrFail();
return redirect('/dashboard')->with('slug', $slug);
}
In route file
you have to mention table name and column name {profile:slug} instead of id
Route::get('/profile/create', 'Controller#createData');
Route::get('/profile/{profile:slug}', 'Controller#getDataBySlug');
Your route definition is wrong please do as above
I have a Model called Type with a title field and a pretty_slug field.
I have a test that is checking that a user can not update a Type instance:
<?
public function test_user_cannot_put_update_page() {
$type = Type::factory()->make([
'title' => 'Original type',
]);
$type->save();
$response = $this->put(route('types.update', [
'pretty_slug' => $type->pretty_slug,
'title' => 'New type',
]));
$response->assertForbidden();
$this->assertDatabaseHas('types', [
'title' => 'Original type'
]);
}
If I do dd($response->getContent()); I can see that a redirect is happening:
Now the weird thing is that I have the exact same for another Model called Level:
<?
public function test_user_cannot_put_update_page() {
$level = Level::factory()->make([
'title' => 'Original level',
]);
$level->save();
$response = $this->put(route('levels.update', [
'pretty_slug' => $level->pretty_slug,
'title' => 'New level',
]));
$response->assertForbidden();
$this->assertDatabaseHas('levels', [
'title' => 'Original level'
]);
}
The Model Level is exactly the same as Type: the same Controller, the same Trait shared, same Policy, same Tests, same routes… I have other Models called Idea, Concept and Episode that have the exact same behavior.
All tests pass, except for my Type Model:
I have no idea why this particular types.update route is not working. It should return a 302 but is instead redirecting.
It is working when I use the webform: the Type instance updates correctly. But the test is failing.
How can I debug this test? Where do I look for an issue in my code?
Thanks for any help.
EDIT 1: added controller and routes
I think the problem is the 'levels.update' route might be protected by the auth middleware.
Since there is no user logged in, the auth middleware will attempt to redirect to the login page instead.
I like IGP's answer. But if that's not it, you might want to check your host configs. Apache or Nginx, or whatever you're using. It could be the route is being called via http and your server is redirecting to https, or visa versa.
Thanks to #Aless55, I found the issue: it was the validation of my Type model that was preventing me from updating the instance.
I looked into the StoreType file, in which I had:
'order' => 'required|numeric',
This means the order field is required. But when I tried calling the types.update route, I wasn't including that field.
One solution would have been to make that field optional. But I ended up including the order field in my test:
$response = $this->put(route('types.update', [
'pretty_slug' => $type->pretty_slug,
'title' => 'Alex new type',
'order' => 1,
]));
I've got a table called Sides which consists of id, name, side_category_id and some other fields not important at the moment.
I wanted to validate that when creating a new side record, the record doesn't exist already. So, let's say I've got in the database a record such as:
id: 1
name: Salad
side_category_id: 3
If I try to insert a new record with name = 'salad' and side_category_id = 3 then the creation must fail and return an error.
I've achieved this by using the following rule:
$rules = [
'name' => 'required',
'side_category_id' => 'required|exists:side_categories,id|unique:sides,side_category_id,NULL,id,name,' . $this->request->get('name')
]
So far so good. It works as it's supposed to. But now it's returning an error if I want to edit a record and save it without any modifications and this is not my desired outcome.
If I try to update the record with no modifications it should succeed. How can I update my rule to achieve this?
you can use Rule::unique()
for create use like this
$rules = [
'name' => ['required'],
'side_category_id' => ['required',Rule::unique('sides', 'name')->where(function ($query) use($category_id) {
return $query->where('side_category_id', $category_id);
}),Rule::exists('side_categories')]
]
for update
$rules = [
'name' => ['required'],
'side_category_id' => ['required',Rule::unique('sides', 'name')->where(function ($query) use($category_id) {
return $query->where('side_category_id', $category_id);
})->ignore($id),Rule::exists('side_categories')]
]
//$id should be you parameter
I am working on a Laravel project and I have the following problem related to validation.
In the past I created this validation rules (related to a new user registration form):
$rules = [
'name' => 'required',
'surname' => 'required',
'login' => 'required|unique:pm_user,login',
'email' => 'required|email|confirmed|unique:pm_user,email',
'pass' => 'required|required|min:6',
'g-recaptcha-response' => 'required|captcha',
];
In particular this rules array contains this rule:
'login' => 'required|unique:pm_user,login',
it seems to me that this last rule check if the inserted login doesn't yet exist into the pm_user table (so it ensure that not exist a row of the pm_user table having the same inserted value into the login column).
Is it? Correct me if I am doing wrong assertion.
If it work in this way now my problem is how to do the opposite thing in another set of validation rule.
In particular I have this other array of rule (defined into a class extendingFormRequest:
public function rules() {
return [
'email' => 'required|email',
'token' => 'required',
];
}
In particular I have to ensure that into the pm_user table yet exist a record having the value of the column named email that is the same of the emai field of the request.
How can I change this request to perform this validation rule?
Laravel 5.4 already has a built in validation rule for this called exists.
https://laravel.com/docs/5.4/validation#rule-exists
I think you are looking for:
'email' => 'required|email|exists:pm_user,email'
These are my rules in my class:
class AppointmentsController extends Controller
{
protected $rules = [
'appointment' => ['required', 'min:5'],
'slug' => ['required', 'unique:appointments'],
'description' => ['required'],
'date' => ['required', 'date_format:"Y-m-d H:i"'],
];
This is in the laravel official docs:
Sometimes, you may wish to ignore a given ID during the unique check.
For example, consider an "update profile" screen that includes the
user's name, e-mail address, and location. Of course, you will want to
verify that the e-mail address is unique. However, if the user only
changes the name field and not the e-mail field, you do not want a
validation error to be thrown because the user is already the owner of
the e-mail address. You only want to throw a validation error if the
user provides an e-mail address that is already used by a different
user. To tell the unique rule to ignore the user's ID, you may pass
the ID as the third parameter:
'email' => 'unique:users,email_address,'.$user->id.',user_id'
I tried using this in my rules:
'slug' => ['required', 'unique:appointments,id,:id'],
This indeed ignores the current row BUT it ignores it completely. What I want to accomplish is, I want it to ignore the current row only if the slug is unchanged. When it is changed to something that is already unique in another row, I want it to throw an error.
The Unique validator works like that
unique:table,column,except,idColumn
So in your case, you can do it like that:
Get the id you want to validate against, you can get it from the route or with any other way that works for you; something like that
$id = $this->route('id');
'slug' => ['required','unique:appointments,slug,'.$id],
For example we need to update contact info into Users table.
In my model User I created this static method:
static function getContactDataValidationRules( $idUserToExcept ) {
return [
'email' => 'required|email|max:255|unique:users,email,' . $idUserToExcept,
'pec' => 'required|email|max:255',
'phone' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'mobile' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'phone2' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:8|max:20',
'recovery_email' => 'required|email|max:255',
];
}
and in my UsersController, into the method that update User I've:
$id = $request->input('id');
$request->validate(User::getContactDataValidationRules( $id ));
:-)