Eloquent Sluggable未按路由参数作用域查询,获取错误Project记录
我有两条路由:/vehicles/vehicle-one/projects/project-one 和 /vehicles/vehicle-two/projects/project-one。使用Eloquent Sluggable包处理Vehicle和Project模型的slug,为Project模型定义了以下方法,确保slug仅在所属车辆范围内唯一:
public function scopeWithUniqueSluggableConstraints(Builder $query, Model $model, $attribute, $config, $slug) { $vehicle = $model->vehicle; return $vehicle ? $query->where('vehicle_id', $vehicle->getKey()) : null; }
这个方法在创建Project记录时正常工作,但访问/vehicles/vehicle-two/projects/project-one时,获取到的却是vehicle-one下的project数据——系统只执行了Project::where('slug', $project->slug)并返回第一条记录,完全没考虑车辆的作用域。
目前我在控制器里用以下代码强制处理:
$project = Project::where('vehicle_id', $vehicle->id)->where('slug', $project->slug)->first();
请问能不能在RouteServiceProvider.php中处理这个问题,让控制器直接获取到正确的模型?
更新信息
路由定义如下:
Route::prefix('vehicles')->name('vehicles.')->group(function () { Route::get('/{vehicle}/projects/{project}', [ProjectController::class, 'show'])->name('projects.show'); });
ProjectController的show方法:
public function show(Vehicle $vehicle, Project $project) { // $project 存储的是Vehicle One下slug为`project-one`的Project,而非Vehicle Two的 }
可以在RouteServiceProvider里通过自定义路由模型绑定实现,让Laravel自动根据当前Vehicle实例筛选对应Project,具体步骤如下:
在
RouteServiceProvider的boot方法中添加自定义绑定逻辑:use App\Models\Vehicle; use App\Models\Project; use Illuminate\Support\Facades\Route; public function boot() { parent::boot(); // 自定义project路由模型绑定,关联当前vehicle Route::bind('project', function ($slug) { // 获取当前路由中的vehicle实例 $vehicle = Route::current()->parameter('vehicle'); // 筛选该vehicle下对应slug的project,找不到则抛出404 return $vehicle->projects()->where('slug', $slug)->firstOrFail(); }); }修改后控制器无需手动查询,直接拿到正确的
Project实例:public function show(Vehicle $vehicle, Project $project) { // 此时$project就是当前$vehicle下对应slug的项目 return view('projects.show', compact('vehicle', 'project')); }
原理说明
默认路由模型绑定仅依据slug全局查询,不会关联父级模型;自定义bind方法先获取路由中的vehicle参数,再通过关联关系查询该车辆下的project,确保返回当前车辆范围内的正确记录,firstOrFail()会自动抛出404异常,符合Laravel默认行为。
替代方案:在模型中处理绑定
也可以直接在Project模型中定义resolveRouteBinding方法,无需修改RouteServiceProvider:
use Illuminate\Database\Eloquent\ModelNotFoundException; public function resolveRouteBinding($value, $field = null) { $vehicle = request()->route('vehicle'); if (!$vehicle) { throw new ModelNotFoundException(); } $project = $this->where('vehicle_id', $vehicle->getKey()) ->where('slug', $value) ->first(); if (!$project) { throw new ModelNotFoundException(); } return $project; }
内容的提问来源于stack exchange,提问作者ryanpitts1

