我在表单请求验证方面遇到了一个小问题,以及如何使用一个 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);
}
那么正确处理的最佳方法是什么?
有什么建议吗? 谢谢你
如果你像这样定义你的 api 路由:
Roue::post('tenants/{tenant}/emails_settings', 'Controller@store');
并修改您的控制器方法,以使用与您的路由定义相匹配的变量名称来键入提示模型:
public function store(EmailSettingValidation $request, Tenant $tenant) {}
然后 Laravel 会自动通过 ID 找到租户并将其注入到控制器中,如果不存在则抛出 ModelNotFoundException (404)。这应该负责验证 id。
授权是另一回事。
所以我发现触发404的解决方案如下:
如果 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);
}
Travis Britz 和 Guillaumehanotel 各有一半的答案,但你仍然遗漏了一个细节。
来自 Travis Britz - 是的,在 URI 中包含tenant_id,以便将其注入到控制器中。 来自 Guillaumehanotel - 还在控制器的 Id 中使用了 Eloquent findOrFail(或者控制器用来执行此操作的任何类,例如存储库或服务类)。
您缺少的最后一部分是处理错误。如果您愿意,可以在控制器中执行此操作,但我通常喜欢为整个系统制定一条规则,即来自
Illuminate\Database\Eloquent\ModelNotFoundException
的异常应始终导致 404。转到
findOrFail()
。我很确定 Laravel 会自动为你生成这个文件的主要版本,但如果你还没有,它应该看起来像这样:
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);
}
}
,它首先通过这里发送所有错误。这就是当您处于调试模式时错误如何呈现为浏览器可以实际解释的内容,而不是直接终止进程。这也让您有机会应用一些特殊规则。
因此,您需要做的就是告诉
try/catch
更改当它看到只能来自
Handler::render()
的错误类型时发生的默认错误代码。 (这种事情就是为什么创建自己的“命名异常”总是好的,即使它们除了继承基类 findOrFail()
之外什么也不做。)只需在
\Exception
返回任何内容之前添加此内容:
render()