Laravel Form Request 表单请求技巧分享

Laravel

首先,如果您不了解 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());
    }
}

让我们在这里花几分钟来了解这里做了什么操作:

  • 我们使用了 edit 方法来显示该表单,该表单将包含几个输入,以便用户更新该帖子。
  • 我们使用 update 方法来更新文章

了解了这里的操作之后,让我们来具体了解一下 update 方法,因为它使用了表单请求验证。

很简单,如果验证出现问题,update 方法的主体不会执行,因为验证会抛出错误消息并重定向会上一个页面(在API处理的情况下,我们可能不需要这样做,因为重定向会导致您出现404错误。我们稍后会对这种情况进行了解)。

注意: 不要使用 $request->all(),因为这消除了表单验证的好处,因为一旦通过验证阶段,您将使用所有提交的表单数据并将其插入数据库,并且这些输入可能包含一个隐藏的或不必要的输入,有可能会导致安全问题.

例如,假设用户在绕过客户端验证后使用了检查元素拦截了请求,并注入了一个 id 字段,如果您未在模型中定义可填充的字段,很可能会导致数据库异常。

相反,您可以使用 $request->validated() ,它仅获取验证通过的数据,只需将其插入数据库即可。

  • Tip: 验证数据的工作原理是,通过表单请求中的 rules 方法返回的数组键名检索请求中的数据

     /**
     * 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;
    }

好吧,我想您已经知道如何使用表单请求,让我们继续。
那有哪些高级用法呢?

  1. 怎样自定义提示消息?
  2. 动态处理授权
  3. API 验证的情况下,如何处理失败的验证和控制重定向。
  4. 授权失败怎么处理?
  5. 如何注入必填的数据,但是您不希望用户提交。
  6. 验证通过后如何自定义传递的值。

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 方法中返回了一个硬编码的布尔值,该值目前没有实际意义。 那么,我们如何动态地控制它呢? 您可以使用通过身份验证的用户来做任何您想做的事情,并深入研究您的角色关系,并且在做完所有事情的最后,您必须返回一个布尔值。 或者,您可以只使用策略和policy,幸运的是,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,
    ];
}

我们在那里指定可以在 路由或控制器上使用的中间件。 如果您想扩展 Authenticatable 类,Laravel 提供了能够通过我们的用户模型使用中间件的功能。


<?php

namespace App\User;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends 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;
}

然后看看 Authorizable trait 的源码,你会看到:


<?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 方法,它使用服务容器来解决我们之前讨论的 gate。 同样,它也需要返回布尔值。 这使得您可以轻松地做到这一点。

<?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 方法;

为了实现上述方法,您必须注册一个策略(policy)并定义一个名为 create-post 的网关(gate),并且它将调用包含您的逻辑的策略(policy)。

  • Laravel 还拥有 artisan 命令,可为您创建策略(policy)。
php artisan make:policy PostPolicy
  • 命名策略的约定是由策略关联的 {ModelName}。

<?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并属于同一表。

好的,我们根据自己的逻辑更新了 update 方法,那我们应该如何把它加入到策略中呢 ?


<?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都会调用failValidation 方法。 如果我们研究一下 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.phprender 方法处进行处理。


<?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的表单请求的源码,可以发现 failedAuthorization 将抛出此异常。
在还不清楚授权失败的原因时, 我们可以再次重写此方法,并自定义此异常,而不是将错误消息直接呈现给最终用户。


<?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 接收请求体和 query 的数据,并在其上应用验证规则,但它并没有直接这样做,而是通过 validationData 的方法验证。 在这里,我们可以覆盖这个方法并注入我们想要的任何数据,然后该方法将被调用,并且将 validationData 返回的内容应用于验证。

我们看看 laravel 的表单请求基类做了什么。


    /**
     * Get data to be validated from the request.
     *
     * @return array
     */
    public function validationData()
    {
        return $this->all();
    }

因为继承了具有 all 方法的 laravel 请求类,所以它可以使用 all 方法返回所有的请求数据。

我们可以通过重写此方法来利用它。

<?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 添加到了验证规则中,该用户肯定不会通过验证,即使用户传递 user_id 通过验证,我们也将使用当前已验证的用户ID覆盖它。

  • 提示: 验证规则为空字符串值意味着没有对该键进行验证, 但是它可以存在,我们可以在使用只获取通过验证的数据的方法时使用它(即:$request->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 ,我们可以使用它直接在数据库中更新/创建记录,而无需准备/关联/附加。

好了,就是表单请求了。 我希望你喜欢它 ✌️

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://dev.to/secmohammed/laravel-form-...

译文地址:https://learnku.com/laravel/t/42954

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 2

这个方法学习到了 :+1:

$request->validated()
3年前 评论
感觉现在 learnku 快多了。
3年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!