Laravel 表单请求提示和技巧。
1. 如何处理自定义消息
2.动态处理授权。
3.处理API时验证失败并控制重定向。
4.处理授权失败。
5.如何注入必须验证但又不希望用户提交的数据。
6.如何自定义验证后传递的请求值。
首先,如果你不理解 Laravel 的表单请求,或者你还没入门,它其实非常容易理解。
本质上,我们使用 Laravel 表单请求来验证传入端点的请求,但将其从控制器中抽象出来,这比在控制器方法中验证请求更为简洁。这开启了重用验证规则的能力,因为它们已被抽象出来。Laravel
允许您在其 Artisan 命令中创建表单请求验证。
php artisan make:request UpdatePostFormRequest
好吧,让我们逐个分析这个命令的输出类来了解其中的内容。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => sprintf(
'required|string|unique:posts,title,%s', $this->post->title
),
'description' => 'required|min:8|max:255|string',
];
}
}
- authorize 方法用于判断用户是否有权执行此请求。因此,它必须返回布尔值。
- rules 方法用于确定验证规则,稍后我们将使用该规则来验证传入的请求。
我们可以按如下方式使用它。
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
use App\Http\Requests\UpdatePostFormRequest;
class PostController extends Controller
{
public function edit(Post $post)
{
return view('posts.edit')->withPost($post);
}
/**
* @param UpdatePostFormRequest $request
*/
public function update(Post $post, UpdatePostFormRequest $request)
{
$post->update($request->all());
}
}
让我们等一会儿来弄清楚这里发生了什么。
- 我们使用编辑方法来显示包含几个输入的表单,以便用户更新该帖子。
- 我们使用了更新方法来更新帖子。
了解了这里发生的事情之后,让我们具体了解一下使用表单请求的更新方法。
很简单,如果验证出现问题,您将无法继续执行更新方法的主体,因为验证会吐出错误消息并将您重定向回去(在 API 处理的情况下我们可能不需要这样做,因为重定向会导致 404 错误。我们稍后会研究这个问题)。
注意:不要使用 $request->all(),因为这会消除表单验证的好处,因为一旦通过验证阶段,您将使用所有提交的表单数据并将它们插入数据库,而这些输入可能包含隐藏的或不需要的输入,这可能会导致安全问题。
例如,假设用户使用了检查元素或在绕过客户端验证后拦截了请求,并注入了一个带有 id 值的输入字段,如果您还没有确定哪些输入是可填写的或者哪些输入是受保护的,这很可能会导致数据库发出警告。
相反,您可以只使用 $request->validated(),它将只获取通过验证的输入,您只需将其插入数据库即可。
- 提示:后台验证方法的工作原理是通过我们在表单请求中的规则方法中的数组的键从请求中检索数据。
/**
* Get the attributes and values that were validated.
*
* @return array
*
* @throws \Illuminate\Validation\ValidationException
*/
public function validated()
{
if ($this->invalid()) {
throw new ValidationException($this);
}
$results = [];
$missingValue = Str::random(10);
foreach (array_keys($this->getRules()) as $key) {
$value = data_get($this->getData(), $key, $missingValue);
if ($value !== $missingValue) {
Arr::set($results, $key, $value);
}
}
return $results;
}
好吧,我想你已经知道如何处理表单请求了,让我们继续吧。
有哪些高级用途?
- 如何处理自定义消息。
- 动态处理授权。
- 在处理 API 时处理失败的验证和控制重定向。
- 处理失败的授权。
- 如何注入必须验证但您不希望用户提交的数据。
- 如何自定义验证后传递的值。
1. 如何处理自定义消息
在编写代码的某个阶段,你可能需要向用户显示一条自定义消息,以便提高可读性。幸运的是,Laravel 提供了一种名为 messages 的方法,可以帮你实现这一点。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => sprintf(
'required|string|unique:posts,title,%s', $this->post->title
),
'description' => 'required|min:8|max:255|string',
];
}
public function messages()
{
return [
'title.unique' => 'title must be a unique.',
'description.min' => 'description minimum length bla bla bla'
];
}
}
您可以像上面一样析构您创建的每个验证规则,并根据需要编写自定义消息。此外,您还可以使用本地化功能,根据用户的语言显示消息。
2.动态处理授权。
你可能注意到了,我们在 authorize 方法中返回了一个硬编码的布尔值,目前这没什么用。那么,我们如何动态地控制它呢?你可以使用经过身份验证的用户做任何你想做的事情,并深入研究你的角色关系,无论你做什么,最终都必须返回一个布尔值。或者,你也可以直接使用策略和门控,幸运的是,Laravel 本身就提供了这些功能。
您是否曾经仔细思考过 app/Http/Kernel.php 文件中的 $routeMiddleware 数组
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
/**
* The application's route middleware.
*
* These middlewares may be assigned to groups or used individually.
*
* @var array
*/
protected $routeMiddleware = [
'auth' => \App\App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\App\Http\Middleware\RedirectIfAuthenticated::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
我们已经拥有了 CAN 中间件,可以在路由/控制器中使用。幸运的是,Laravel 允许我们通过扩展 Authenticatable 接口,在用户模型中使用 CAN 中间件的功能。原因如下。
<?php
namespace App\User;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
}
如果你看一下 Authenticatable,它是 Illuminate\Foundation\Auth\User。
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;
class User extends Model implements
AuthenticatableContract,
AuthorizableContract,
CanResetPasswordContract
{
use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}
然后,如果您查看可授权特征,您将在那里看到它。
<?php
namespace Illuminate\Foundation\Auth\Access;
use Illuminate\Contracts\Auth\Access\Gate;
trait Authorizable
{
/**
* Determine if the entity has a given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function can($ability, $arguments = [])
{
return app(Gate::class)->forUser($this)->check($ability, $arguments);
}
/**
* Determine if the entity does not have a given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function cant($ability, $arguments = [])
{
return ! $this->can($ability, $arguments);
}
/**
* Determine if the entity does not have a given ability.
*
* @param string $ability
* @param array|mixed $arguments
* @return bool
*/
public function cannot($ability, $arguments = [])
{
return $this->cant($ability, $arguments);
}
}
看一下 can 方法,它使用服务容器来解析我们之前讨论过的门。此外,它返回布尔值。这让你可以轻松地做到这一点
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostFormRequest extends FormRequest
{
public function authorize()
{
return auth()->user()->can('update-post', $this->post);
}
}
- 注意:为了使用当前经过身份验证的用户,请确保此路由受到 auth 中间件的保护,以避免崩溃。除非您尝试在 null 上使用 can 方法。
为了实现上述方法,您必须注册一个策略并定义一个名为“create-post”的门,它调用包含您的逻辑的策略。
- Laravel 还有一个工匠命令可以为您制定政策。
php artisan make:policy PostPolicy
- 命名策略的约定是 {ModelName} 与 Policy 连接。
<?php
namespace App\Policies;
use App\{User, Post};
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
-
Laravel 会保留所有策略方法的第一个参数,并为您检索用户。如果您需要其他模型,可以将其作为其他参数传递给该方法,在本例中为 post 模型。
-
注意:在现实世界的例子中,我热衷于使用声明式编程风格,此时可以像这样遵循。
public function update(User $user, Post $post)
{
return $user->is($post->user);
// or with extra check if we have roles the declarative way.
// return $user->is($post->user) && $user->roles->contains($roleModel);
}
这是更具声明性的方式,因为我们没有在这里放置任何负担代码并且不能重用的逻辑,尽量避免这种情况。
- 注意:如果您不知道“is”方法是什么,它用于确定两个模型是否具有相同的 ID 并属于同一张表。
好吧,我们为更新方法创建了逻辑,如何将其绑定到门?
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', 'App\Policies\PostPolicy@update');
//
}
}
恭喜,现在我们可以实现我们之前所做的事情,因为我们通过使用 can 方法将门名称“update-post”绑定到我们的策略逻辑。
3.处理API时验证失败并控制重定向。
默认情况下,只要我们创建的任何规则未通过,Laravel 就会调用 failedValidation 方法。如果我们深入研究 Laravel 提供的表单请求中的这个方法,就能理解为什么会出现重定向。
<?php
namespace Illuminate\Foundation\Http;
class FormRequest extends Request implements ValidatesWhenResolved
{
/**
* Handle a failed validation attempt.
*
* @param \Illuminate\Contracts\Validation\Validator $validator
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
protected function failedValidation(Validator $validator)
{
throw (new ValidationException($validator))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
}
嗯,我们在那里遇到了错误包,并且出现了重定向,这对于 API 请求来说非常烦人。该如何解决这个问题?🙄
让我们创建自己的表单请求,扩展 laravel 的表单请求来覆盖它。
<?php
namespace App\Http\Requests;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\Exceptions\HttpResponseException;
abstract class APIRequest extends FormRequest
{
/**
* Determine if user authorized to make this request
* @return bool
*/
public function authorize()
{
return true;
}
/**
* If validator fails return the exception in json form
* @param Validator $validator
* @return array
*/
protected function failedValidation(Validator $validator)
{
throw new HttpResponseException(response()->json(['errors' => $validator->errors()], 422));
}
abstract public function rules();
}
然后,在用于 API 端点的任何表单请求中,我们可以使用此类而不是 laravel 提供的表单请求。
4.处理授权失败。
每当授权失败时,Laravel 默认会抛出异常,您可以在 app/Exceptions/Handler.php 的 render 方法中处理该异常
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* @param Exception $execption
* @return void
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.
*
* @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof AuthorizationException) {
return response()->json([
'message' => $exception->getMessage(),
], 401);
}
return parent::render($request, $exception);
}
}
如果您不知道这里发生了什么,每当抛出异常时,laravel 都会通过 render 方法,然后呈现此异常,我们可以检查即将发生的异常并拦截它并返回我们自己的消息或任何您想要的内容。除非让 laravel 渲染。
好吧,但是我们怎么知道抛出的异常是授权异常呢?如果我们检查 Laravel 的表单请求类,就会发现授权失败的方法会抛出这个异常。
我们可以再次重写这个方法,并使用我们自己的消息抛出这个异常,而不是直接向最终用户呈现“未经授权的尝试”这样的信息,因为这种信息在某些方面不够清晰。
<?php
use Illuminate\Auth\Access\AuthorizationException;
class UpdatePostFormRequest extends FormRequest
{
public function failedAuthorization()
{
throw new AuthorizationException("You don't have the authority to update this post");
}
}
它不仅与通过自定义消息抛出此异常有关,还可以自由实现所需的任何逻辑。
5.如何注入必须验证但又不希望用户提交的数据。
我记得以前我曾经需要这个,但现在我记不清当时的情况了。总之!我会教你如何实现,你可能会在特定场景下用到它。
Laravel 接受传递的请求输入和查询并对其应用验证规则,但它并不直接执行此操作,而是使用一种名为 validationData 的方法。此时,我们可以覆盖它并注入任何我们想要的内容,并且将调用此方法并将验证应用于从 validationData 返回的内容。
让我们看一下 laravel 的基本表单请求,它在那里做什么。
/**
* Get data to be validated from the request.
*
* @return array
*/
public function validationData()
{
return $this->all();
}
它返回所有请求数据,并使用 all 方法,因为我们扩展了具有 all 方法的 laravel 请求类。
我们可以通过覆盖此方法来利用它。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => sprintf(
'required|string|unique:posts,title,%s', $this->post->title
),
'description' => 'required|min:8|max:255|string',
'user_id' => ''
];
}
protected function validationData()
{
return array_merge($this->all(), [
'user_id' => $this->user()->id
]);
}
}
我们将 user_id 添加到验证规则中,用户肯定不会通过该规则,即使用户通过了,我们也会用当前已验证的用户 ID 覆盖它。
- 提示:如果为某个键传递空字符串值,则表示该键无需验证。但是,该值可以存在,我们可以在使用 validated 时使用它(因为它是我们 rules 方法中数组的键)。
6.如何自定义验证后传递的请求值。
我想你可能已经猜到了,因为我们有验证方法,我们可以覆盖它并将来自原始验证方法的值与我们想要的任何值合并。
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UpdatePostFormRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => sprintf(
'required|string|unique:posts,title,%s', $this->post->title
),
'description' => 'required|min:8|max:255|string',
];
}
public function validated()
{
return array_merge(parent::validated(), [
'user_id' => $this->user()->id
]);
}
}
此时,用户尚未传递其 user_id,当我们调用 validated 方法时,实际上是调用了我们在此处创建的 validated 方法,该方法会覆盖 Laravel 默认提供的方法。我们获取父级验证值并将其与 user_id 合并,这样我们就自动注入了 user_id,并且可以使用它直接在数据库中更新/创建记录,而无需进行准备/关联/附加操作。
好了,表单请求就到这里。希望你喜欢它✌️,下篇文章再见🙈。如果你喜欢这篇文章,别忘了关注我👀
文章来源:https://dev.to/secmohammed/laravel-form-request-tips-tricks-2p12