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.
Related
In Laravel 7 fetching a model was pretty straightforward, i just needed to setup mi resource route and make a get to the address:
http://localhost/test/public/employee/1
But i cant make it work on Laravel 8, according to my understanding i just need to do this:
public function show(Employee $employee)
{
dd($employee);
}
But dd only returns an empty class:
If i do this:
public function show(Employee $employee)
{
dd(Employee::find(1));
}
dd returns the correct data:
Route::resources([
'employee' => EmployeeController::class,
]);
Can somebody help me find what am i missing?
Regards...
Route::resource('employee', EmployeeController::class);
https://laravel.com/docs/8.x/controllers#resource-controllers
Your Route must have the same variable like
Route::get('/employee/{employee}', 'EmployeeController#show');
And ensure you have binding middleware enabled for this route. ->middleware(['bindings']);
Updating for Resource routing:
Route::resource('/employee', 'EmployeeController')->middleware('bindings');
The problem was that i was naming the routes in spanish:
Route::apiResource('empleados', EmployeeController::class);
And because of this Laravel is expecting to receive the model encapsulated in a spanish verb class (empleados instead of employee), So i needed to rename the parameter inside the method controllers to receive the correct model:
public function show(Employee $empleado)
{
return $empleado;
}
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.
I'm building a shopping app using Laravel where each product's URL must be kept concise.
Instead of using the following permalink structure: (which is common, but unfavorable)
www.example.com/products/{product-slug}
I want to use this permalink structure:
www.example.com/{product-slug}
In order to accomplish this, I'm using an implicit route model binding in my routes file:
Route::get( '{product}', function ( App\Product $product ) {
return view( 'product' ); // this works, but is not what I want
});
And I am overriding the lookup behavior in my Product model:
class Product extends Model
{
public function getRouteKeyName()
{
return 'slug'; // use the 'product.slug' column for look ups within the database
}
}
Now, according to Laravel's documentation:
Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
(View Source)
So I know that Laravel will match the {product} variable to a product stored within my database, or return a 404 response if one is not found.
And this all makes sense to me...
However...
Each product page is unique, so after the route matches a {product}, that {product} object needs to be passed to a controller for further processing.
So how do I pass this route to a controller, if I want to keep my implicit model binding?
Point the route to a controller function.
This would be your route (I named the controller ProductController and pointed it to show function, but you can rename both to your liking):
Route::get( '{product}', 'ProductController#show');
And this would be in your ProductController:
public function show(Request $request, \App\Product $product)
{
// Do stuff with $product
// Return view and pass it the $product variable
return view('product', compact('product'));
}
To answer my own question, I think I've found a great solution that combines my initial approach and devk's response:
Credits to Arjun's Blog for the idea.
As it turns out, you can also perform implicit model binding within a controller by passing an Eloquent model as a dependency:
/* App/Http/Controllers/ProductController.php */
/**
* Get the Product object.
*
* #param App\Models\Product
*/
public function show( Product $product )
{
return view( 'product', compact( 'product' ) );
}
Even though we are now referencing the model using a controller, Laravel will still automatically resolve the model. In fact, this behavior is clearly defined within the documentation:
Laravel automatically resolves Eloquent models defined in routes or
controller actions whose type-hinted variable names match a route
segment name.
(View Source)
I must have missed those words when I read it the first time...
Now, in order to set up the {product-slug} route (in the way I wanted to), you must set up your model and route definitions like so:
/* App/Models/Product.php */
class Product extends Model
{
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
}
As mentioned earlier, overriding the getRouteKeyName() method will make Laravel search for a product using it's slug column in the database instead of its id column (which is the default).
/* routes/web.php */
Route::get( '{product}', 'ProductController#show' );
In our routes file, we still name our parameter {product} (instead of {product-slug}) because the name of the parameter must match the name of the Eloquent model.
Using this configuration, requests made on:
www.example.com/{product-slug}
will return a product page if the provided {product-slug} matches one stored inside the database. If a product is not found, a 404 Not Found response will be returned instead.
However, because we are binding this route to the base path /, this means that every URL requested by a client will be passed through this configuration.
To avoid this problem, make sure that your route definitions are in proper order within your routes file (from greatest to least precedence), and use validation when conflicts occur.
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 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;
});