Laravel 9 API中验证路由参数Buyer ID的存在性与归属权最优方案
Laravel 9 API路由参数验证优化方案
问题背景
我正在开发一个Laravel 9 API项目,涉及三个模型:Buyer、BuyerTier、BuyerTierOption,关联关系为:Buyer拥有BuyerTier,BuyerTier拥有BuyerTierOption。
创建BuyerTier的请求URL为 {{endpoint}}/api/company/1/buyers/1/tiers/,URL中已包含buyer_id,需要验证该ID不仅存在,还必须属于当前公司。目前的实现是在请求中额外传入buyer_id字段,通过自定义规则ValidModelOwnership验证合法性与归属,但认为Laravel应该有更适配路由参数的原生实现方式,希望了解遗漏的方案。
当前代码实现
/** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store($company_id, $buyer_id, Request $request) { $this->authorize('create', BuyerTier::class); $validator = Validator::make($request->all(), [ 'name' => [ 'required', 'string', Rule::unique(BuyerTier::class) ->where('buyer_id', $buyer_id) ->where('company_id', $company_id) ], 'buyer_id' => [ 'required', 'numeric|min:50', new ValidModelOwnership(Buyer::class, [ ['company_id', $company_id], ['id', $request->input('buyer_id')] ]) ], 'country_id' => [ 'required', 'numeric', new ValidModelOwnership(Country::class, [ ['company_id', $company_id], ['id', $request->input('country_id')] ]) ], 'product_id' => [ 'required', 'numeric', new ValidModelOwnership(Product::class, [ ['company_id', $company_id], ['id', $request->input('product_id')] ]) ], 'processing_class' => 'required|string|alpha_num', 'is_default' => [ 'required', 'boolean', new ValidDefaultModel(BuyerTier::class, $buyer_id) ], 'is_enabled' => 'required|boolean' ]); if ($validator->fails()) { return response()->json([ 'message' => 'One or more fields has been missed or is invalid.', 'errors' => $validator->messages(), ], 400); } try { $tier = new BuyerTier; $tier->user_id = Auth::id(); $tier->company_id = $company_id; $tier->buyer_id = $buyer_id; $tier->country_id = $request->input('country_id'); $tier->product_id = $request->input('product_id'); $tier->name = trim($request->input('name')); $tier->description = $request->input('description') ?? null; $tier->processing_class = $request->input('processing_class'); $tier->is_default = $request->boolean('is_default'); $tier->is_enabled = $request->boolean('is_enabled'); $tier->save(); return response()->json([ 'message' => 'Buyer tier has been created successfully', 'tier' => $tier ], 201); } catch (\Exception $e) { return response()->json([ 'message' => $e->getMessage() ], 400); } }
原生优化方案
1. 路由模型绑定+查询范围验证归属
Laravel的路由模型绑定可以自动将路由参数转换为模型实例,结合查询范围可直接验证归属:
步骤1:更新路由定义
将路由中的buyer_id替换为模型绑定:
Route::post('/company/{company}/buyers/{buyer}/tiers', [BuyerTierController::class, 'store']);
步骤2:在Buyer模型添加查询范围
// app/Models/Buyer.php public function scopeForCompany($query, $companyId) { return $query->where('company_id', $companyId); }
步骤3:自定义路由模型绑定
在RouteServiceProvider中注册绑定逻辑,确保仅返回当前公司的Buyer实例:
// app/Providers/RouteServiceProvider.php public function boot() { parent::boot(); Route::bind('buyer', function ($value) { $companyId = request()->route('company'); return Buyer::forCompany($companyId)->findOrFail($value); }); }
当路由中的buyer_id不属于当前公司时,Laravel会自动返回404响应,无需手动编写验证逻辑。
2. 控制器内直接验证路由参数
如果不使用模型绑定,可在控制器开头直接查询验证buyer_id的归属:
public function store($company_id, $buyer_id, Request $request) { $this->authorize('create', BuyerTier::class); // 验证buyer_id归属,不存在则返回404 Buyer::where('id', $buyer_id)->where('company_id', $company_id)->firstOrFail(); // 移除原验证规则中的buyer_id字段验证 $validator = Validator::make($request->all(), [ 'name' => [ 'required', 'string', Rule::unique(BuyerTier::class) ->where('buyer_id', $buyer_id) ->where('company_id', $company_id) ], 'country_id' => [ 'required', 'numeric', new ValidModelOwnership(Country::class, [ ['company_id', $company_id], ['id', $request->input('country_id')] ]) ], 'product_id' => [ 'required', 'numeric', new ValidModelOwnership(Product::class, [ ['company_id', $company_id], ['id', $request->input('product_id')] ]) ], 'processing_class' => 'required|string|alpha_num', 'is_default' => [ 'required', 'boolean', new ValidDefaultModel(BuyerTier::class, $buyer_id) ], 'is_enabled' => 'required|boolean' ]); // ... 后续逻辑不变 }
3. 利用原生exists规则的条件约束
Laravel的exists规则支持添加额外查询条件,可直接验证路由参数,无需自定义规则:
public function store($company_id, $buyer_id, Request $request) { $this->authorize('create', BuyerTier::class); // 将路由参数合并到验证数据中 $validationData = array_merge($request->all(), [ 'buyer_id' => $buyer_id ]); $validator = Validator::make($validationData, [ 'buyer_id' => [ 'required', 'numeric|min:50', Rule::exists('buyers')->where(function ($query) use ($company_id) { $query->where('company_id', $company_id); }) ], // 其他字段验证规则不变 ]); // ... 后续逻辑不变 }
这种方式无需在请求中额外传入buyer_id,直接复用路由参数完成验证。
内容的提问来源于stack exchange,提问作者Ryan H
相关产品推荐
相关产品推荐

