I have a Laravel model PurhaseOrder and a controller PurchaseOrdersController with a method show
If I typecast the method with an an interger:
public function show(int $purchaseOrder)
{
dd(PurchaseOrder::find($purchaseOrder));
}
Then dd() dumps out what I expect ( the record in the database where the id matches $purchaseOrder)
However if instead I do this:
public function show(PurchaseOrder $purchaseOrder)
{
dd($purchaseOrder);
}
Then $purchaseOrder is an empty model and not populated with any data from the database. My route looks like this:
Route::get('purchase-orders/{purchase_orders}/show', ['as' => 'admin.purchase-orders.show', 'uses' => 'PurchaseOrdersController#show']);
This is code that I've inherited and I'm trying to update from Laravel 5.3 to Laravel 5.6. Where should I be looking to solve this issue of the model not being populated correctly?
The implicit model binding requires you to match the variable name with the route parameter name, so for example, for the following method:
public function show(PurchaseOrder $purchaseOrder)
{
dd($purchaseOrder);
}
The route should contain the matching parameter name, for example:
Route::get('purchase-orders/{purchaseOrder}/show', [
'as' => 'admin.purchase-orders.show',
'uses' => 'PurchaseOrdersController#show'
]);
Notice that, the method parameter name in show method which is $purchaseOrder and the route parameter name {purchaseOrder} both are same and that's a requirement for the implicit model binding, otherwise you have to do an explicit model binding, where you've to explicitly tell the framework about your parameter name, for example (in RouteServiceProvider):
public function boot()
{
parent::boot();
Route::model('purchase_orders', App\PurchaseOrder::class);
}
This will tell the framework that, if there is the {purchase_orders} parameter name available in the route then resolve/bind an instance of PurchaseOrder model into the given method for that route.
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
For reference I used this post and Laravel documentation:
Laravel 5.6 getRouteKeyName() not working
https://laravel.com/docs/5.8/routing#explicit-binding
In my routes I have a resources array like this:
Route::resources([
...
'state' => 'StateController',
...
]);
In my controller I am trying to access models by the slug. My state table and model has name and slug columns.
StateController
public function show(State $state)
{
dd($state);
// return view('state.show', compact('state'));
}
public function getRouteKeyName()
{
return 'slug';
}
If I remove the State model typecast it prints out the string indiana for the url: http://codebase.localhost.com/state/indiana But when I put the typecast back in, it gives me a 404. It can't find the model.
I thought getRouteKeyName was supposed to retrieve the model by the string passed.
What gives?
Here are my models fillables
'name', 'order', 'slug'
It's definitely a record in my table.
Looks like the issue is with the getRouteKeyName method being defined in your controller.
From the docs:
you may override the getRouteKeyName method on the Eloquent model
Try moving the getRouteKeyName method into your State model.
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'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.
I try to set some specific filter on all controller methods with:
public function __construct() {
$this->beforeFilter(function(){
//whathever
});
}
and it's working well on normal GET methods, problem occures when there is some POST method:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => function($direction) {
$controller = new CMSSettingsController();
return $controller->doMoveMenu($direction);
}));
after click in a button which send POST with $direction, I'v got
Call to a member function filter() on a non-object
in vendor/laravel/framework/src/Illuminate/Routing/Controller.php
protected function registerClosureFilter(Closure $filter)
{
$this->getFilterer()->filter($name = spl_object_hash($filter), $filter);
return $name;
}
If I use already registred filter it's working, so what's going on?
I have few controllers which need specific function todo before running controller methods, so I can't make global universal filter. Is there any other good solution?
The problem could be that you are calling the controller action directly instead of letting the Router do it for you. When the router tries to apply the filters, instead of applying them on the controller, it ends up attempting to apply them on the output of the doMoveMenu action - which, of course, is not a Controller object and has no method filter.
Instead, your route should look like this:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => 'CMSSettingsController#doMoveMenu'));
The reason you don't need to do the method call manually is that since your Route has a parameter in it and your method accepts a parameter, the Router will automatically pass the parameter into the action method. Additionally, since you are providing a method name as the uses value, Laravel knows that it has to instantiate the Controller and run the filters.