I'm setting up a new laravel installation and have come to an issue with implicit route model binding when using a uuid as an id.
My route:
Route:group(['prefix' => 'admin'], function(){
Route:resource('users', 'Admin\UserController');
});
The show method of Admin\UserController:
public function show(App\User $user) {
dd($user);
}
So when I hit the URL my.app/admin/users/long-uuid-string-here I would expect to see the user information but I get an empty User object.
When I add the following to the RouteServiceProvider, it works as expected:
$router->model('admin/users', \App\User::class);
Is there something I am missing, does implicit model binding expect an integer? Is it because it is in a route group or something else?
Yes! the id exists in the database, and I am using laravel 5.2
Since you are using resource routing, the route will be like:
Route::get('admin/users/{users}', 'Admin\UserController#show');
Note the {users} variable. It's plural. So in your show method:
change this:
public function show(App\User $user) {
dd($user);
}
to
public function show(App\User $users) {
dd($users);
}
It's a bit weird, but thats the problem.
Related
My routes:
Route::apiResource('courses', 'CourseController');
Route::apiResource('courses.classrooms', 'ClassroomController');
List: php artisan route:list
api/v1/courses/{course}
api/v1/courses/{course}/classrooms/{classroom}
My question is: all my functions in classroom controller needs the course, something like that
public function index($course_id)
{
$classroom = Classroom::where('course_id', $course_id)->get();
return $classroom;
}
public function store($course_id, Request $request)
{
// ...
$classroom->course_id = $course_id;
// ...
}
public function show($course_id, $id)
{
$classroom = Classroom::where('course_id', $course_id)->find($id);
return $classroom;
}
// ...
Have some Policy/Helper in Laravel to accomplish this automatically?
I believe it's not necessary to add the property $course_id in all functions manually, what can I do?
You can use a group to enclose all your routes. Something like:
Route::group(['prefix' => '{course}'], function () {
// you can place your routes here
});
So all the routes that exist in that group will already have the course value in the url path and you don't have to "rewrite it" for every route.
If that field is set by you for example an env variable then inside your RouteServiceProvider you can put the prefix you want in the mapApiRoutes function.
protected function mapApiRoutes()
{
Route::prefix('/api/v1/courses/'.config('app.myVariable'))
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
That way ALL your api endpoints will start with that prefix and you can have it in all the endpoints.
If the routes are registered correctly like you posted, your methods in the ClassroomsController should receive an additional parameter that's the course id fragment from the url.
For example if you request /api/v1/courses/1/classrooms route, the controller will receive the correct {course} parameter set to 1 as the first parameter.
You could then implement the index method of the ClassroomsController to use implicit model binding and get the Course instance with the given url id for the course.
To do so you have to type-hint the Course model for the first function's parameter and name the variable as the url fragment you want to use to retrive your model.
In your code example, you should do:
public function index(Course $course)
{
return $course->classrooms;
}
Note: I assume you have a relationship between Course and Classroom models to retrive the classrooms from the course model instance
You can read more about that on the official documentation here
I have two routes:
Route::get('subjects/{subject}/{tag?}', 'SubjectController#show');
Route::get('subjects/{subject}/{tag}/{lesson}','LessonController#show');
When I hit the first route, it works properly but when I hit the second route, I get the following error response:
Sorry, the page you are looking for could not be found.
Is this because laravel is trying to treat the /{tag}/{lesson} portion of 2nd route as the value of the parameter of 1st route?
My controller methods are as follows:
//SubjectController.php
public function show($subjectSlug, $tag = null)
{
dd('Inside SubjectController#show');
}
//LessonController.php
public function show(Subject $subject, Tag $tag, Lesson $lesson)
{
dd('Inside LessonController#show');
}
When I visit, say,
localhost:3000/subjects/mysubject-slug/1
It matches the first route and responds accordingly, but when I visit,
localhost:3000/subjects/mysubject-slug/1/mylesson-slug
it shows the page not found error. How can I fix this?
As mentioned in the comments, because of Route Model Binding you can end up with a 404 when the model for the binding can not be retrieved. When using implicit route model binding the primary key will be used to search against by default. This can be changed on the model to use a different field, in this case the slug field.
"If you would like model binding to use a database column other than id when retrieving a given model class, you may override the getRouteKeyName method on the Eloquent model"
Laravel 5.5 Docs - Routing - Route Model Binding - Implicit Binding
public function getRouteKeyName()
{
return 'slug';
}
Try to change your controller to
//LessonController.php
public function show($subject, $tag, $lesson)
{
dd('Inside LessonController#show');
}
And see if it gets hit. If it does, your binding is done incorrectly.
On the side note, I suppose you don't have Route::resource() set up somewhere up in the routes file?
I am working on a Laravel 5.5 application. When I use php artisan make:model SomeModel -mr it creates the model, migration and resource controller.
I've been noticed that some methods have by default only one parameter: the model:
public function show(SomeModel $someModel)
{
...
}
If you look into the $someModel variable it has an empty SomeModel object.
I was reading on Laravel Documentation that it looks like the Containers or Facades but I am not sure how to use this. Do you?
Edit 1:
I had my routes defined in routes/web.php as: Route::resource('users', 'UserController');
Now I had to define all the routes manually since automatic binding was not working:
Route::get('users', 'UserController#index');
Route::get('users/create', 'UserController#create');
Route::post('users', 'UserController#store');
Route::get('users/{user}/edit', 'UserController#edit', function(App\User $user) {});
Route::post('users/{user}', 'UserController#update', function(App\User $user) {});
Route::post('users/{user}/delete', 'UserController#destroy', function(App\User $user) {});
So, should I replace every resource controller route to manual routing like this?
The resource controller is expecting you to use route model binding. In your routes file, each route that corresponds to a controller action with an injected model will need to have a matching parameter.
For example:
Route::get('user/{user}', 'UserController#show');
Using the above route, the following controller action would receive a user instances that corresponds to the user ID passed as a URL parameter.
class UserController extends Controller
{
public function show(User $user)
{
...
}
}
The reason you're seeing an empty model now is that Laravel will just pass and fresh model to the controller if it is not bound to a route parameter. In other words, if you forget to bind the model in your routes file automatic injection will just give you a new instance.
Note that if you are using a route resource the resulting routes should already have the correct parameters
Route::resource('users', 'UserController');
You can run php artisan route:list to confirm that your actual routes are correct.
Your problem is your controller is expecting two parameters like below:
public function show($id, User $user)
if you try:
public function show(User $user)
it should work correctly.
In your route you are passing only a single param like:
user/{user}
So if you dd the first param it will display the number 1 but if you pass that
to the model it will return the corresponding user as per what id you pass in the route.
The reason your User model was returning an empty object was because there was no value passed to it.
Also make sure your route placeholder: /{user} matches the variable name in
the controller: public function show(User $user).
Hope this helps.
I too came across with the same problem.
If your model having two or more words, you have to use only small letters like $modeModel as $somemodel.
public function show(SomeModel $somemodel)
{
...
}
I have made eloquent-sluggable work on my app. Slugs are saved just fine. Buuuut... How do I use it to create a pretty url?
If possible, I would like to use them in my url instead of ID numbers.
Yes, you can use slug in your route and generated url, for example, if you declare a route something like this:
Route::get('users/{username}', 'UserController#profile')->where('profile', '[a-z]+');
Then in your controller, you may declare the method like this:
public function profile($username)
{
$user = User::where('username', $username)->first();
}
The username is your slug here and it must be a string because of where()... in the route declaration. If an integer is passed then route couldn't be found and 404 error will be thrown.
As of Laravel 5.2, if you use Route Model Binding, then you can make your routes that contain the object identifier as usual (Implicit Binding). For example:
In routes/web.php (Laravel 5.3) or app/Http/routes.php (Laravel 5.2):
Route::get('categories/{category}', 'CategoryController#show');
In your CategoryController:
show (Category $category) {
//
}
The only thing you need to do is telling Laravel to read the identifier from a different column like, for example, slug column, by customizing the key name in your eloquent model:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
Now, you can refer your url's that requires the object identifier with the slug identifier instead the id one.
See Laravel 5.3 (or 5.2) Route Model Biding
For future readers, as of Laravel 8.0, you can specify a column right in the path
Route::get('/users/{user:slug}', function (User $user) {
return $user->bio;
});
I have seen other topics regarding this issue, didn't work out.
So in Laravel 5.4 Route Model Binding, we can bind a route to a model like:
define the route in web.php:
web.php:
Route::get('/users/{user}', UsersController#show);
UsersController#show:
public function show(User $user){
// now we already have access to $user because of route model binding
// so we don't need to use User::find($user), we just return it:
return view(users.show, compact('user'));
}
The above code will work just fine, so in our controller we can return the $user without finding the user, we already have it.
but imagine this:
web.php:
Route::patch('/users/archive', UsersController#archive);
EDITED: now the above line makes a patch route and we don't have {user} in the route url, the user id is being posted via the form.
UsersController#archive:
public function archive(Request $request, User $user){
// how can I access the $user here without using User::find($user);
// I get to this action via a form which is posting `user` as a value like `5`
dd($request->user); // this now echo `5`
// I can do:
// $user = User::find($request->user);
// and it works, but is there a way to not repeat it every time in every action
}
What I have tried:
in RouteServiceProvider::boot() I have:
Route::model('user', 'App\User');
The above is what i have found in Google, but not working.
I would appreciate any kind of help.
EDIT:
It seems it's not called Route Model Binding anymore since we don't have the {user} in the route and that's because my code is not working, the user variable is being posted to the controller and it's only accessible via $request->user.
this is route model binding:
Route::patch('users/{user}/archive', UsersController#archive);
this is not:
Route::patch('users/archive', UsersController#archive);
since we don't have {user} and it's being posted via the form and could be accessed only via $request->user.
(please correct me if I am wrong about the definition of route model binding)
SO:
what I want to achieve in a nutshell: in every request being sent to my UsersController, if I am sending user variable as a post variable, it must be bounded to User::findOrFail($request->user) and then $user must be available in my controller actions.
I want to achieve this because in every action I am repeating myself doing User::findOrFail($request->user) and I don't want to do that, so I want to check in every request if I have a variable name like a model name, they should be bounded.
There's no need to bind explicitly to the User class, so Route::model('user', 'App\User'); should be removed; type-hinting should be enough instead.
public function archive(Request $request, User $user) { ... }
should be working, just make sure you are importing the right User class at the top of the file (use App\User;).
Then the model is in your $user variable (method argument), try dd($user).
It's clear now that since the {user} variable is not in the URI, this is not a route model binding issue. You just want the User instance injected as a parameter based on the contents of the request.
$this->app->bind(User::class, function () {
$user_id = request('user') ?: request()->route('user');
return User::findOrFail($user_id);
});
You could add that to the register method in the AppServiceProvider (or any other registered provider) to have the model injected. I leave it to you to generalize this to other model classes.
You don't even need (Request $request) in your controller.
If you correctly imported User class, as alepeino said, you can access all user values from Model with this syntax $user-><value> for example:
public function archive(User $user) {
$userId = $user->id;
}
According to update.
If you use POST request, you can access it's data with such code request()->get('<variable you send as parameter>')
For example:
public function archive() {
$userId = request()->get('user');
$userInfo = User::find($userId);
//Or as you said
$user = User::findOrFail(request()->get('user'));
}
Can you try this;
public function archive(Request $request, $u = User::find($user){
//now variable $u should point to the user with id from url
}