I have this route defined:
Route::get('/categories/{category}/{country}/{state?}/{county?}/{city?}', ['App\Http\Controllers\LocationController', 'show'])->withScopedBindings();
public function show(Category $category, Country $country, State $state = null, County $county = null, City $city = null) {
echo 'ok';
}
It's fine, automatically checks for relationships, works with 1, 2 or 3 of optional parameters. But... I want to extend it so it that the COUNTY is not always mandatory. Cause there are some cities that have direct relationship to state without county_id in the middle. Cities table has county_id and also state_id and always only one of them is present. If I add:
Route::get('/categories/{category}/{country}/{state?}/{city?}', ['App\Http\Controllers\LocationController', 'show'])->withScopedBindings();
Only one of the routes are working.
How can I fix this? Thanks.
CodePudding user response:
You can define two separate routes
Route::get(
'/categories/{category}/{country}/{state?}/{county?}',
['App\Http\Controllers\LocationController', 'show']
)
->withScopedBindings()
->name('categories.show.county');
Route::get(
'/categories/{category}/{country}/{state?}/{county?}/{city?}',
['App\Http\Controllers\LocationController', 'show']
)
->withScopedBindings()
->name('categories.show.county.city');
And then check for the route name in the controller
public function show(
Category $category,
Country $country,
State $state = null
) {
if(
request()->route()->named('categories.show.county') &&
request()->route()->hasParameter('county')
) {
$county = County::where(
(new County)->getRouteKeyName(),request()->route('county')
)
->firstOrFail();
}
if(request()->route()->named('categories.show.county.city')) {
if(request()->route()->hasParameter('county') {
$county = County::where(
(new County)->getRouteKeyName(),request()->route('county')
)
->firstOrFail();
}
if(request()->route()->hasParameter('city')) {
$city = City::where(
(new City)->getRouteKeyName(),request()->route('city')
)
->firstOrFail();
}
}
}
CodePudding user response:
So, following Donkarnash answer I finally have a solution.
Route::get('/categories/{category}/{country}/{state?}/{county_slug?}/{city_slug?}',
['App\Http\Controllers\LocationController', 'show'])->scopeBindings();
public function show(
Category $category,
Country $country,
State $state = null,
$county_slug = null,
$city_slug = null
) {
if ($county_slug && $city_slug)
{
// two parameters present, that means the chain is state -> county -> city
$county = County::where('state_id', $state->id)
->where('slug', $county_slug)
->firstOrFail();
$city = City::where('county_id', $county->id)
->where('slug', $city_slug)
->firstOrFail();
} else {
if ($county_slug) {
// one parameter present, that means the chain is state -> county OR state -> city
$county = County::where('state_id', $state->id)
->where('slug', $county_slug)
->first();
if (!$county) {
$city_slug = $county_slug;
$city = City::where('state_id', $state->id)
->where('slug', $city_slug)
->first();
}
if (!$county && !$city) {
abort(404);
}
}
}
}
And then in migrations states table:
$table->unique(['slug', 'country_id']);
Counties table:
$table->unique(['slug', 'state_id']);
Cities table:
$table->unique(['slug', 'state_id']);
$table->unique(['slug', 'county_id']);
And it works. Only drawback is that if there is a county and a city with the same slug that belong to the same state. For example, county with slug "test" and state_id "15" and city with slug "test" and state_id "15". Then it won't work correctly and result as county. But usually ALL cities for a country have a chain of country -> state -> county -> city OR country -> state -> city, so this minor drawback won't affect final results for the website. Nevertheless, this also can be fixed by adjusting request validation rules.