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;
});
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'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 am working on a school project. while working on a schools detail page I am facing an issue with the URL. My client needs a clean URL to run AdWords. My school detail page URL: http://edlooker.com/schools/detail/4/Shiksha-Juniors-Ganapathy. But he needs it like http://edlooker.com/Shiksha-Juniors-Ganapathy. If anyone helps me out it will be helpful, thanks in advance.
You need to define this route after all routes in your web.php (if laravel 5.x) or in routes.php (if it is laravel 4.2).
Route::get('{school}','YourController#getIndex');
And your controller should be having getIndex method like this,
public function getIndex($school_name)
{
print_r($school_name);die; // This is just to print on page,
//otherwise you can write your logic or code to fetch school data and pass the data array to view from here.
}
This way, you don't need to use the database to get URL based on the URL segment and you can directly check for the school name in the database and after fetching the data from DB, you can pass it to the school details view. And it will serve your purpose.
Check Route Model Binding section in docs.
Customizing The Key Name
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:
/**
* Get the route key for the model.
*
* #return string
*/
public function getRouteKeyName()
{
return 'slug';
}
In this case, you will have to use one front controller for all requests and get data by slugs, for example:
public function show($slug)
{
$page = Page::where('slug', $slug)->first();
....
}
Your route could look like this:
Route::get('{slug}', 'FrontController#show');
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.