Laravel路由:同一控制器方法中兼容主域名与子域名的多参数处理问题
Laravel路由:同一控制器方法中兼容主域名与子域名的多参数处理问题
我来帮你搞定这个头疼的问题!核心矛盾出在路由参数的传递/接收顺序不匹配,以及子域名参数无法直接设为可选这两点上,完全不需要重复写控制器方法,咱们用两种思路就能解决:
方案一:调整参数顺序+关联数组传参(快速修复)
1. 修正控制器方法的参数接收顺序
你当前的showByCategory方法把$category_slug放在了$subdomain前面,但子域名路由里的{subdomain}是路由的第一个参数,导致参数错位。把顺序反过来,并且利用Laravel的路由参数自动绑定,就不用手动从$request里取了:
public function showByCategory(Request $request, $subdomain = null, $category_slug) { // 这里直接用$subdomain即可,不需要再$subdomain = $request->route('subdomain'); $region_id = $this->regionService->getRegionIdByDomain($subdomain); // 剩下的逻辑保持不变... $categories = Category::whereNull('category_id')->get(['id', 'name', 'slug']); $regions = Region::with('districts')->get(['id', 'name', 'slug']); $services = Service::get(['id', 'name', 'slug', 'icon']); $category_id = Category::where('slug', $category_slug)->value('id'); $sights = Sight::where('category_id', $category_id)->filter($request); if (!is_null($region_id)) { $sights = $sights->where('region_id', $region_id); } $sights = $sights->latest()->paginate(20); return view('main.sights.index', compact('sights', 'categories', 'regions', 'services')); }
2. Blade模板用关联数组传参(避免顺序错误)
之前用顺序数组传参很容易踩坑,换成按参数名传参的关联数组,不管路由参数顺序怎么变都不会错:
@if(!is_null($subdomain)) <a href="{{ route('subdomain.main.sights.category.index', ['subdomain' => $subdomain, 'category_slug' => $sight->category->slug]) }}" class="btn btn-outline-weldonBlue"> {{ $sight->category->name[app()->getLocale()] }} </a> @else <a href="{{ route('main.sights.category.index', ['category_slug' => $sight->category->slug]) }}" class="btn btn-outline-weldonBlue"> {{ $sight->category->name[app()->getLocale()] }} </a> @endif
3. 补全路由的语法错误
注意你主域名的路由代码里少了一个大括号,这会导致路由解析失败,赶紧补上:
Route::middleware(['language'])->group(function () { Route::domain('vizit.loc')->group(function () { // 这里补上缺失的{ Route::controller(FrontSightController::class)->group(function () { Route::get('/sights', 'index')->name('main.sights.index'); Route::get('/sights/category/{category_slug}', 'showByCategory')->name('main.sights.category.index'); }); }); });
方案二:用中间件注入子域名(更优雅的长期方案)
如果不想纠结参数顺序,可以写一个中间件,把subdomain参数统一注入到请求中,控制器直接从请求里拿就行:
1. 创建中间件
执行命令生成中间件:
php artisan make:middleware InjectSubdomain
然后编辑app/Http/Middleware/InjectSubdomain.php:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; class InjectSubdomain { public function handle(Request $request, Closure $next) { // 从路由里获取subdomain参数,没有则设为null $subdomain = $request->route('subdomain') ?? null; // 把subdomain存入请求属性 $request->attributes->set('subdomain', $subdomain); return $next($request); } }
2. 注册中间件
打开app/Http/Kernel.php,把中间件加到$routeMiddleware数组里:
protected $routeMiddleware = [ // 其他中间件... 'inject.subdomain' => \App\Http\Middleware\InjectSubdomain::class, ];
3. 更新路由组
把这个中间件加到你的语言中间件组里,或者直接加到两个路由组中:
// 子域名路由组 Route::middleware(['language', 'inject.subdomain'])->group(function () { Route::domain('{subdomain}.vizit.loc')->group(function () { Route::controller(FrontSightController::class)->group(function () { Route::get('/sights', 'index')->name('subdomain.main.sights.index'); Route::get('/sights/category/{category_slug}', 'showByCategory')->name('subdomain.main.sights.category.index'); }); }); }); // 主域名路由组 Route::middleware(['language', 'inject.subdomain'])->group(function () { Route::domain('vizit.loc')->group(function () { Route::controller(FrontSightController::class)->group(function () { Route::get('/sights', 'index')->name('main.sights.index'); Route::get('/sights/category/{category_slug}', 'showByCategory')->name('main.sights.category.index'); }); }); });
4. 简化控制器方法
现在控制器方法里不需要接收$subdomain参数了,直接从请求里拿:
public function showByCategory(Request $request, $category_slug) { $subdomain = $request->get('subdomain'); $region_id = $this->regionService->getRegionIdByDomain($subdomain); // 剩下的逻辑和之前一致... }
这个方案的好处是,不管后续路由怎么调整,控制器都不需要改参数,逻辑更统一。
两种方案都能解决你的问题,方案一适合快速修复,方案二更适合长期维护。选一个你觉得顺手的就行!
备注:内容来源于stack exchange,提问作者SlimBoy Fat
相关产品推荐
相关产品推荐

