You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:10:25