Laravel 验证请求和 API 路由 POST 参数

Posted

技术标签:

【中文标题】Laravel 验证请求和 API 路由 POST 参数【英文标题】:Laravel Validation Request & API route POST parameter 【发布时间】:2019-04-13 02:17:23 【问题描述】:

我在表单请求验证以及如何使用一个 API 路由处理它方面遇到了一个小问题。

我需要创建的资源依赖于其他资源。

(这里的 EmailSettings 属于租户)

所以我的路线看起来应该是这样的:/api/tenants/id/email_settings

我的请求验证需要几个字段,包括 tenantId :

public function rules() 
    return [
        'email' => 'bail|required|email|unique:email_settings,email',
        'name' => 'bail|required',
        'username' => 'bail|required',
        'password' => 'bail|required'
        'imapHost' => 'bail|required',
        'imapPort' => 'bail|required',
        'imapEncryption' => 'bail|required',
        'imapValidateCert' => 'bail|required',
        'smtpHost' => 'bail|required',
        'smtpPort' => 'bail|required',
        'smtpEncryption' => 'bail|required',
        'tenantId' => 'bail|required',
    ];

我这样发送请求:

try 
    const response = await this.tenantForm.post('/api/tenants')
    let newTenant = helpers.getNewResourceFromResponseHeaderLocation(response)
    let tenantId = parseInt(newTenant.id);
    try 
        await this.emailSettingsForm.post('/api/tenants/' + tenantId + '/email_settings')
        this.requestAllTenants()
     catch (response) 
        $('.second.modal').modal(blurring: true, closable: false).modal('show');
    
 catch (response) 
    $('.first.modal').modal(blurring: true).modal('show');

因此,tenantId 作为参数而不是在请求正文中传递以遵守 REST 约定。 但问题出在我的控制器中,当我合并数据以创建资源时,仅在合并之前对正文数据进行了验证。

public function store(EmailSettingValidation $request, $tenant_id) 
    $emailSetting = $this->emailSettingService->create(
        array_merge($request->all(), compact($tenant_id))
    );
    return $this->response->created($emailSetting);

那么正确处理它的最佳方法是什么?

在正文中传递 id ?看起来很乱 使用验证器手动验证?我希望保留表单验证 删除tenantId规则并手动检查?

有什么建议吗? 谢谢

【问题讨论】:

你的租户表有模型吗? @TravisBritz 是的,但为什么它很重要? 【参考方案1】:

如果你这样定义你的 api 路由:

Roue::post('tenants/tenant/emails_settings', 'Controller@store');

并修改您的控制器方法以使用与您的路由定义匹配的变量名称对模型进行类型提示:

public function store(EmailSettingValidation $request, Tenant $tenant) 

然后 Laravel 会自动通过 ID 找到租户并将其注入控制器,如果不存在则抛出 ModelNotFoundException (404)。这应该负责验证 id。

授权是另一回事。

【讨论】:

您的解决方案运行良好(如果我们将中间件“绑定”添加到路由中)但是当租户不存在时,它会返回“500 : No query results for model [App\Tenant]”而不是 404,我不知道为什么:/ @guillaumehanotel 这是什么版本的 Laravel? 这是5.5版 @guillaumehanotel 您的web 中间件组是否包含\Illuminate\Routing\Middleware\SubstituteBindings::class, 是的,但我在 api.php 中定义了我的路由,因此网络组不适用【参考方案2】:

所以我发现触发 404 的解决方案如下:

从 EmailSettings Validation 中删除tenantId 添加提供程序以在发生异常“ModelNotFoundException”时添加自定义错误,如下所示 No query results for model in Laravel with Dingo - how to make a RESTful response on failure?

如果 ID 无效,请尝试使用 findOrFail 方法抛出此异常:

public function store(EmailSettingValidation $request, $tenant_id) 
    Tenant::findOrFail($tenant_id);
    $emailSetting = $this->emailSettingService->create(
        array_merge($request->all(), ['tenantId' => $tenant_id])
    );
    return $this->response->created($emailSetting);

【讨论】:

【参考方案3】:

Travis Britz 和 Guillaumehanotel 各有一半的答案,但你仍然缺少一个细节。

来自 Travis Britz - 是的,在 URI 中包含tenant_id,以便将其注入控制器。 来自 Guillaumehanotel - 还在控制器中的 Id 中使用了 Eloquent findOrFail(或控制器用来执行此操作的任何类,如存储库或服务类)。

您缺少的最后一块是处理错误。如果您愿意,您可以在 Controller 中执行此操作,但我通常喜欢为我的整个系统制定一条规则,即来自 findOrFail()Illuminate\Database\Eloquent\ModelNotFoundException 异常应始终导致 404。

转到app/Exceptions/Handler.php。我很确定 Laravel 会自动为你生成这个文件的肉和土豆版本,但如果你还没有,它应该看起来像这样:

<?php

namespace App\Exceptions;

use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;

/**
 * Class Handler
 * @package App\Exceptions
 */
class Handler extends ExceptionHandler

    /**
     * Render an exception into an HTTP response.
     *
     * For our API, we need to override the call
     * to the parent.
     *
     * @param  \Illuminate\Http\Request $request
     * @param  \Exception $e
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $error)
    
        $exception = [
            'title' => 'Internal Error',
            'message' => $error->getMessage();
        ];
        $statusCode = 500;
        $headers = [
            'Content-Type', 'application/json'
        ];

        return response()->json($exception, $statusCode, $headers, JSON_PRETTY_PRINT);
    

Laravel 基本上有一个系统范围的try/catch,它首先通过这里发送所有错误。当您处于调试模式时,这就是错误如何呈现为浏览器可以实际解释的内容,而不是直接终止进程。这也让您有机会应用一些特殊规则。

因此,您需要做的就是告诉Handler::render() 更改在看到只能来自findOrFail() 的错误类型时出现的默认错误代码。 (这就是为什么创建自己的“命名异常”总是好的原因,即使它们除了继承基类 \Exception 之外什么都不做。)

只需在 render() 返回任何内容之前添加:

if ($error instanceof Illuminate\Database\Eloquent\ModelNotFoundException) 
    $statusCode = 404;

【讨论】:

这已经是 Laravel 的默认行为了。 500 状态码的原因是他们使用的外部库 (dingo/api) 有自己的异常处理。

以上是关于Laravel 验证请求和 API 路由 POST 参数的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 路由、api 和 web 用例

由用户会话验证的Laravel API路由

如何从外部 API 获取 $_POST 数据 - LARAVEL

laravel 从控制器调用路由

Laravel AJAX 请求进入路由 api Vue 错误

内部请求需要 Laravel Dingo 身份验证 JWT