Route precedence order - php

I have 2 routes with their methods written in same controller[LinkController]:
Route::get('/{country}/{category}', ['as' => 'tour.list', 'uses' => 'LinkController#tourlist']);
Route::get('/{category}/{slug}',['as' => 'single.tour', 'uses' => 'LinkController#singleTour']);
And my methods are:
public function tourlist($country, $category)
{
$tour = Tour::whereHas('category', function($q) use($category) {
$q->where('name','=', $category);
})
->whereHas('country', function($r) use($country) {
$r->where('name','=', $country);
})
->get();
return view('public.tours.list')->withTours($tour);
}
public function singleTour($slug,$category)
{
$tour = Tour::where('slug','=', $slug)
->whereHas('category', function($r) use($category) {
$r->where('name','=', $category);
})
->first();
return view('public.tours.show')->withTour($tour);
}
My code in view is:
{{$tour->title}}
The trouble i am having is the second route [single.tour] returns the view of the first route [tour.list]. I tried to return other view also in 2nd method but still it returns the view of first method. Does laravel have routing precedence ?

This is happening because, Laravel matches routes from the file, and the route which comes earlier and matches the pattern will execute first, you can use regex pattern technique to avoid this like:
Route::get('user/{name}', function ($name) {
//
})->where('name', '[A-Za-z]+'); // <------ define your regex here to differ the routes
See Laravel Routing Docs
Hope this helps!

Both your routes consist of two parameters in the same place. That means any url that matches route 1 will also match route 2. No matter in what order you put them in your routes definition, all requests will always go to the same route.
To avoid that you can specify restrictions on the parameters using regular expressions. For example, the country parameter may only accept two letter country codes, or the category parameter may have to be a numeric id.
Route::get('/{country}/{category}')
->where('country', '[A-Z]{2}')
->where('category', '[0-9]+');
https://laravel.com/docs/5.3/routing#parameters-regular-expression-constraints

Related

Laravel - Nested relation causes previous filters to be ignored

I do a specific relation query all over the application, where I only need the User's subscriptions that have active column set to true.
And I have a scope method in User model, which applies said filter, to avoid copy/paste, like:
public function scopeWithActiveSubscriptions($query)
{
$query->with([
'subscriptions' => function ($query) {
$query->where('active', true);
},
]);
}
Now sometimes I want to eager-load the plan of each subscription, too.
For that I tried something like:
$user = User::where('id', 1)
->withActiveSubscriptions()
->with('subscriptions.plan')
->first();
$subscriptionList = $user->subscriptions;
But query results to all subscriptions,
in other words, ignores the ->where('active', true) part (of scope method).
How can I make this work correctly?
A quick solution would be modifying the scopeWithActiveSubscriptions method to allow it to accept another optional parameter that tells it which additional relations should also be included and thus you don't loose your filtering.
public function scopeWithActiveSubscriptions($query, array $with = [])
{
// just merges hard coded subscription filtering with the supplied relations from $with parameter
$query->with(array_merge([
'subscriptions' => function ($query) {
$query->where('active', true);
}
], $with));
}
Now you can tell that scope which nested relations you want to include and you no longer need to call with to include them by yourself.
$user = User::where('id', 1)
->withActiveSubscriptions(['subscriptions.plan'])
// ->with('subscriptions.plan') // no longer needed as we're telling the scope to do that for us
->first();
$subscriptionList = $user->subscriptions;
With that you can pass custom relations to the scope something like (am improvising here just for demo purposes)
$user = User::where('id', 1)
->withActiveSubscriptions([
'subscriptions.plan' => fn($q) => $q->where('plans.type', 'GOLD')
])->first();
Learn more about Laravel's Eloquent Scopes.
Hope i have pushed you further.
Seems Laravel does not have yet any chainable (Builder-style) solution (for asked situation), and we ended up editing the scope filter.
Into something like:
public function scopeWithPendingSubscriptions(Builder $query, $subRelations = null)
{
$query->with([
'subscriptions' => function (HasMany $query) use ($subRelations) {
$query->where('active', '=', true);
if ($subRelations) {
$query->with($subRelations);
}
},
]);
}
Which allows me to do query like:
// ...
->withActiveSubscriptions('plan');
Instead of my old (not working) code, which was:
// ...
->withActiveSubscriptions()
->with('subscriptions.plan');
Note that even passing nested-filters is now possible, like:
// ...
->withActiveSubscriptions(['plan' => function ($query) {
$query->where('name');
}]);
(Basically same as Laravel's ->with(...) method.)

Laravel 7, Missing required parameters for [Route: /xxxxx] error

I am trying to pass a value from a view to a controller and use that value in a query. I am getting Missing parameter error. This is my code.
View:
A
Route:
Route::get('/first_letter/{$f_letter}','PtoductController#first_letter')
->name('first_letter');
Controller:
public function index()
{
$products = DB::table('products')
->join('families', 'products.id_family', '=', 'families.id')
->select('products.name')
->where('products.name', 'like', $f_letter.'%')
->get();
return view('product.index', compact('products'));
}
You have two problems
first, fix your route to be like this
Route::get('/first_letter/{f_letter}','PtoductController#first_letter')
->name('first_letter');
when you pass a wildcard in route it is written like this {f_letter} not {$f_letter}
Second, in your controller you need to pass the wildcard in your method as parameter like this
public function index($f_letter)
{
$products=DB::table('products')
->join('families', 'products.id_family', '=', 'families.id')
->select('products.name')
->where('products.name', 'like', $f_letter.'%')
->get();
return view('product.index', compact('products'));
}
you can read more about routing parameters here
At last you can make your a tag more clear like this
A
First, are you sure the controller name is "PtoductController"? Shouldn't it be called ProductController?
Then, if the controller name is correct, remove "$" from the link you call from the controller.
Route::get('/first_letter/{f_letter}', ['uses' => 'PtoductController#first_letter'])
->name('first_letter');
Last, Put $f_letter as a parameter in the "first_letter" function.
public function index($f_letter)
{
//your code
}

Laravel set Array in Route

Hello StackOverflow Community, after some research we decided to ask you for the solution.
We would like to have a link with a defined array. The goal would be to have a link such as:
www.testurl.com/restaurants/CUISINENAME/
Then, we would just like to see all restaurants with the particular cuisine. The filter is currently working on the website with a checkbox.
Router
Route::group(['prefix' => 'restaurants', 'namespace' => 'frontEnd', 'middleware'=>'checkzipcode'], function () {
Route::get('/', 'RestaurantController#showAllRestaurants');
Route::post('/', 'RestaurantController#showAllRestaurants');
});
Controller
if (request('cusineName')) {
if (is_array(request('cusineName'))) {
$cusineName = request('cusineName');
} else {
$cusineName = (explode(",", request('cusineName')));
}
$all_restaurant = $all_restaurant->whereIn('restaurant_cuisines.type_cuisine_id', $cusineName);
}
We were thinking of setting the array into the controller. Does anyone have any other suggestions?
Firstly, your array check is fine. However, you should return false if no cusineName has been passed through (As there is no point continuing).
You then are able to do an eloquent query for restaurants which have a particular cuisine by using the whereHas() method:
...
$restaurants = Restaurant::whereHas('cuisine', function($query) use ($cuisines) {
$query->whereIn('id', $cuisines);
})->get();
...
In the example, we pass through $cuisines to be used within the whereHas() eloquent method, to then use in the whereIn() method. This will check against the array if the cuisine's id is found.

how we can pass multiply id in laravel

My view.blade.php code here
<a href="{{ url('p_grid') }}/{{($cat_id)}}/{{$row->sub_id}}">
My route code here
Route::resource('p_grid', 'BasicController#p_grid');
And my BasicController code here
public function p_grid(Request $request, $id)
{
echo "success";
if ($id == 1) {
$r = DB::table('sub_category')
->select('*')
->where('cat_id', $id)
->where('sub_status', '1')
->orderBy('sub_id', 'asc')
->get();
$cat_name = DB::table('category')
->where('cat_id', $id)
->get();
$count = DB::table('products')
->where('sub_id', $id)
->count();
return view('buy-and-sell/p_grid', compact('r','cat_name','count','id'));
}
click on anchor tag to show this error
The extra parameter in your URL is causing the 404. Laravel doesn't expect the URL to have multiple id's, because you are using resourceful routing. You will need to append your routes.php file to account for this:
Route::resource('p_grid', 'BasicController#p_grid');
Route::get('p_grid/{category}/{subcategory}', [
'as' => 'category-with-subcategory',
'uses' => 'BasicController#gridWithSubcategory',
]);
And make sure you have a gridWithSubcategory method in your BasicController.php file.
That said, I'd advise you to understand better what Laravel is doing when you declare Route::resource(), because I would question how much of it you really need. It's really just a shorthand for registering ordinary routes (see here for the full list and specifications) like index, create, show, destroy, etc. If you want to see the routes your app actually has, after being parsed by Laravel (including routes not in your routes.php, which could have been registered by third-party packages) type php artisan route:list on a CLI.
Finally, I'd strongly suggest using route names throughout your app for better clarity and portability. The artisan command above will give you route names, so you can use something like this in your views:
<a href="{{ route('category-with-subcategory', ['category' => $cat_id, 'subcategory' => $row->sub_id]) }}">
That way, if you ever want to change your route's footprint, the view won't break (as long as you maintain the parameter requirements).
My view.blade.php code here
<a href="{{ url('p_grid', ['category' => $cat_id, 'subcategory' => $row->sub_id]) }}">
My route code here
Route::get('p_grid/{category}/{subcategory}', [
'as' => 'category-with-subcategory',
'uses' => 'BasicController#p_grid'
]);
And my BasicController code here
public function p_grid(Request $request, $id)
{
echo "success";
}
success image upload and work done
enter image description here

Laravel routes confusion with slugs

How can I handle 2 similar urls in my routes.php in Laravel Framework ?
Eg :
mysite/shoes (categories page)
mysite/adidas-model-1 (product pages)
Code :
#Categories Pages
Route::get('{catSlug}', array('uses' => 'CategoriesController#show'));
#Product Page
Route::get('{productSlug}', array('uses' => 'ProductController#show'));
If i browse to mysite/shoes show method in CategoriesController is fired, but if I browse to mysite/adidas-model-1 it's not show method of ProductController but the one of CategoriesController which is fired.
Is there a nice way to achieve this in routes.php file ? Or do I route all to CategoriesController#show and if object is not found fire the show method of ProductController ?
Thanks.
In the two routes you've shown, the router has no way of knowing when you're entering a catSlug and when you're entering a productSlug - they're both strings and there's no code there to distinguish them.
You can correct this by adding a where clause:
Route::get('{catSlug}', array('uses' => 'CategoriesController#show'))
->where('catSlug', '[A-Za-z]+');
Route::get('{productSlug}', array('uses' => 'ProductController#show'))
->where('productSlug', '[-A-Za-z0-9]+');
In the regular expressions above, I've assumed that categories are strings of upper and lower case letters only - no numbers, no spaces, no punctuation - and products include hyphens and numbers.
I should also add that the order of these declarations will be important. The product route also matches the category route, so the category route should be declared first, so it has a chance to fire. Otherwise, everything looks like a product.
Thanks for your answers.
I really need to be free of what I choose for my slugs. So i found an other solution.
# Objects (cats or products)
Route::get('{slug}', array('uses' => 'BaseController#route'));
and in my BaseController file :
public function route($slug)
{
// Category ?
if($categories = Categories::where('slug', '=', $slug)->first()){
return View::make('site/categories/swho', compact('categories'));
// Product ?
}elseif($products = Products::where('slug', '=', $slug)->first()){
return View::make('site/products/show', compact('products'));
}
}
I first test for a Category object (I have less categories than products) and if not found I test for a product.
Try to make it like this, this is how I use it.
Route::get('{slug}', function($slug) {
// IF IS CATEGORY...
if($category = Category::where('slug', '=', $slug)->first()):
return View::make('category')
->with('category', $category);
// IF IS PRODUCT ...
elseif($product = Product::where('slug', '=', $slug)->first()):
return View::make('product')
->with('product', $product);
// NOTHING? THEN ERROR
else:
App::abort(404);
endif;
});

Categories