Multiple wildcards in Laravel resource routing - php

I declared resource route like below
Route::resource('{slug}/user','ManageUsersController');
This gives me a route
{slug}/user/{id}
where slug is the slug of a company name and id is the specific id of user.
In view, I declared anchor tag : <a href=user/{!! $user->id!!}>User</a>
This directs me to the function show in ManageUserController
public function show($id)
{
return $id;
}
But the return result is the slug, not the the id of the user. My URL is project/company-name/user/5
Where did i go wrong ? how can i get the user ID in return rather than the slug ?
-thanx

you have to catch both slug and id in your method,so your method will like
public function show($slug, $id)
{
//for slug
return $slug;
//for id
return $id;
}

Pass both parameters to your controller method, because Laravel will consider the first parameter to be the first parameter in the route (which would be your slug).

Related

Laravel apiResource to fetch record by field other than id

I am using Laravel to fetch records from the database for which I have created an apiResource controller. I have setup the following code inside routes.
Route::apiResource('/MyController',MyController::class)->middleware('auth:api');
In MyController.php my code to display a specific data is:
/**
* Display the specified resource.
*
* #param \App\Models\ContentType $contentType
* #return \Illuminate\Http\Response
*/
public function show(MyModel $Model)
{
// show content type info
return response([
'data' => new MyControllerResource($Model)
],200);
}
I get the data when I place an api call like this:
http://localhost:8000/api/MyController/1
What I want is a record getting fetched by passing other field value instead of id in the route. For example.
http://localhost:8000/api/MyController/mypost
Any idea how can I achieve this?
The route key name defaults to id for all models. You will want to update this to name or whatever field "mypost" is by adding a getRouteKeyName() method.
<?php
namespace App;
...
class Post extends Model
{
public function getRouteKeyName()
{
return 'name';
}
...
}
You are using route model binding. And in laravel its default behaviour is to find model with id and return collection. It will not search for any other field. Of course you can change this behaviour but it can only search data by one field. To change this behaviour use getRouteKeyName method in model like:
public function getRouteKeyName()
{
return 'another_field_in_my_table';
}
You can also use explicit binding like:
Route::bind('MyController', function($value) {
return \App\MyModel::where('id', $value)->orWhere('another_field_in_my_table', $value)->first();
});
To learn more about explicit binding check docs.
You'll have to define route separately. You can group the routes by controller and middleware though. And once done, then, Inside your route, you need to change to this:
Route::get('/show/{post:columnName}', [ MyController::class, 'show' ])->middleware('auth:api');
Now your data will be fetched on the basis of your column name defined in the route.

How to change Route Model Binding finding data from id to slug

I have a route like the following.
Route::get('/articles/{articleSlug}' ,
[App\Http\Controllers\ArticleController::class, 'single']);
And the method of single() at ArticleController class goes here:
public function single($slug)
{
$article = Article::where('slug',$slug)->first();
$article->increment('viewCount');
return view('home.article',compact('article'));
}
Now I wish to use Route Model Binding for finding this data from the articles table based on the column slug. But as I know, Route Model Binding finds data based on the id. So how to change Route Model Binding finding data from id to slug ONLY for ArticleController.php (meaning that the other Controller classes can work with id as route model binding)?
In case you want to use other model field as the biding attribute instead of id you can define a getRouteKeyName which return the name of the field which must be use
class Article extends Model {
// other methods goes here
public function getRouteKeyName() {
return 'slug';
}
}
Or you can pass the field name directly when you define the route like this
Route::get('/articles/{article:slug}' , [App\Http\Controllers\ArticleController::class, 'single']);
With this code inside of your controller you must ensure that the name provide as parameter in the route definition match the name of the controller argument
public function single(Article $article)
{
$article->increment('viewCount');
return view('home.article',compact('article'));
}
Your controller is already set up, all you need to do is change your variable name to $slug in the route, and I believe that should be enough:
Route::get('/articles/{slug}' , [App\Http\Controllers\ArticleController::class, 'single']);
change your route to this:
Route::get('/articles/{article:slug}' , [App\Http\Controllers\ArticleController::class, 'single']);
and then inject the Article model to your controller function and let laravel do the rest for you:
public function single(Article $article)
{
$article->increment('viewCount');
return view('home.article',compact('article'));
}
you can customize route model bindings directly in the route definition:
past given code in app/model/Article.php:
public function getRouteKeyName()
{
return 'slug';
}
2.when you use slug change route to
Route::get('/articles/{article:slug}' , [App\Http\Controllers\ArticleController::class, 'single']);
to use id sample change slug to id
Route::get('/articles/{article:id}' , [App\Http\Controllers\ArticleController::class, 'single']);
you can add bind method to your model boot() like this
public function boot()
{
Route::bind('article', function ($value) {
return Article::where('slug', $value)->firstOrFail();
});
}
to learn more about it read this section in the Laravel docs
https://laravel.com/docs/9.x/routing#customizing-the-resolution-logic

Laravel - 404 | Not Found upon "/{id}" entry

I am getting a "404 | Not Found" error when i try to access a specific item from my database. The items with the specific ID's do exist in the database, but i am not even sure if that even has any influence on the problem.
My routing looks likes this:
Route::prefix('departments')->group(function() {
Route::get('/{id}', [DepartmentController::class, 'showDepartment']);
});
And the related controller looks like this:
public function showDepartment() {
return '';
}
}
I haven't yet finished the function. I just wanted to check if the routing even worked by returning an empty string.
So what am i doing wrong? Is it the routing or the controller?
According to the Laravel documentation, you have to define the parameter in the route then use it in the controller as a parameter.
in your controller:
public function showDepartment($id) {
return 'Hello';
}
The id is not bound to any model to fetch from the database to do that you can use Laravel route model binding
for example when you have a model named Department you write your route like this:
Route::get('/{department}', [DepartmentController::class, 'showDepartment']);
and in the controller:
public function showDepartment(Department $department) {
return 'Hello from depratment';
}
When your department exists it returns the response otherwise return 404.
You may need a Department model class. Then you can find the item from database by id $department = Department::findOrFail($id);
you are send parameter in route and this function without any parameter
route :
Route::prefix('departments')->group(function() {
Route::get('/{id}', [DepartmentController::class, 'showDepartment']);
});
your function in controller should be
public function showDepartment($id) {
$department = Department::findOrFail($id);
}
}

Use different ID for show route in laravel

Show route works with /example/{id}. So when I create a product with an id = 1 , show/1 will show me that product.
However, I want to make a unique key for id, so only people with the url can view it. Unique id = 21J2#29SAHAS198S so product 1 will show with /show/21J2#29SAHAS198S.
I have tried to make a parameter in migrations 'key' which is $random = str_random(40); I then set $key to primary id in the table model. This still doesnt work
In your products table create a filed code something like this and define it as unique and at time of product creation insert random string in it and then specify this filed in your Product model as the route key like this.
Product Model
class Product extends Model {
public function getRouteKeyName()
{
return 'code';
}
}
Now define in your route show/{product};
Route::get('show/{product}', 'ProductController#show');
Controller
class ProductController extends Controller {
public function show(Request $request, App\Product $product){
dd($product)
}
}
For details check Implicit Binding in https://laravel.com/docs/5.6/routing#implicit-binding
You can use base64 encode to encode the ID parameter when you create URL for example
example/{base64_encoded_id}.
And when you use that ID parameter in your code then just use base64 decode function to get original ID.
You can use Regular Expression Constraints like below:
Route::get('example/{id}', function ($id) {
//..
})->where('id', '/[a-z0-9A-Z#]*/');
For detail information regarding Laravel Route with regular expression follow below link:
https://laravel.com/docs/5.6/routing#route-parameters
if you want set string $key to Primary Keys, you must set
public $incrementing = false
protected $keyType = 'string'
in your Product Model
See https://laravel.com/docs/5.6/eloquent#Primary-Keys

Trying to make SEO friendly urls without using database

I'm building my first Laravel app. I have posts that belongs to different categories.
I'm trying to make SEO friendly urls /categories/category_name for categories without saving a 'slug' in database.
Here is what I came with so far:
My category model:
class Category extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
public function getSlugAttribute(): string
{
return str_slug($this->name);
}
public function getUrlAttribute(): string
{
return action('CategoriesController#index', [$this->id, $this->slug]);
}
}
My category controller
class CategoriesController extends Controller
{
public function index($id, $slug = '')
{
$posts = Category::findOrFail($id)->posts;
return view('index', compact('posts'));
}
}
My route:
Route::get('/categories/{slug}', 'CategoriesController#index')->name('category');
With {{ $post->category->url }} I get something like http://localhost/category/1?alpha but it works and display the appropriate posts. Route is returning an error.
How I get ride of the id? and make the route works?
Thank you in advance for your help!
I'm assuming that you have a human readable category name (ex My Category) and you are converting that to a slug in your URLs (ex my-category).
A naive approach to doing this would be to attempt to reverse the slug and lookup the category by name, but there are use-cases where this won't work.
class CategoriesController extends Controller
{
public function index($slug)
{
$name = ucwords(str_replace('-', ' ', $slug));
$posts = Category::where('name', '=', $name)->first()->posts;
return view('index', compact('posts'));
}
}
The problem with that approach is there are cases where information is lost when converting the name to a slug, for example if the proper category name contains a hyphen, the hyphen becomes a space when converting the resulting slug.
You can approach this a different way, without storing the slug, by modifying your route:
Route::get('/categories/{category}/{slug}', 'CategoriesController#index')->name('category');
In this case, you pass both the ID and the slug in the URL. You can either fetch the category from the ID or use route model binding to fetch it automatically. This technically means you can pass any string as the slug and it will still work. You can handle this by determining the actual slug and redirecting to the canonical URL if the end user provides an incorrect slug.
class CategoriesController extends Controller
{
public function index(Category $category, $slug)
{
if(str_slug($category->name) != $slug) {
// Redirect to the canonical URL
}
$posts = $category->posts;
return view('index', compact('posts'));
}
}
A benefit of keeping both the ID and the slug in the URL is you get redirection for free: If the category name changes, the new URL will work and the old URL will automatically redirect to the new one.
Side note: If you look the URL for his question you'll notice this is the approach StackOverflow uses.
I don't think this is possible. The reason behind is that you will need to search for the category using the slug but is not stored in the database, so you will need to convert the slug to its original value.
For example, if you have a category named "Modern art", the slug would look something like modern-art. If you pass that slug to your controller, you need to convert it back to "Modern art" to be able to retrieve the category by its name. But maybe the real name was "Modern Art" or "Modern art!" or something else.
I recommend you storing the slug in the database.

Categories