如何在laravel中修改验证后的请求输入


How to modify request input after validation in laravel?

我发现了Request::replace方法,它允许替换Request中的输入参数。

但是目前我只能看到一种实现它的方法-在每个控制器动作中编写相同的替换输入代码。

是否有可能以某种方式分组代码,这将在请求成功验证后执行,但在控制器动作开始之前?

例如,我需要在我的api中支持ISO2语言,但在引擎盖下,我必须将它们转换为真正存储在数据库中的遗留语言。目前我有这个代码在控制器:

// Controller action context
$iso = $request->input('language');
$legacy = Language::iso2ToLegacy($iso);
$request->replace(['language' => $legacy]);
// Controller action code starts

我认为你正在寻找的是ValidatesWhenResolvedTrait特征中的passedValidation()方法

如何使用:

  1. 创建自定义请求:php artisan make:request UpdateLanguageRequest
  2. 将验证规则放入UpdateLanguageRequest类中的rules()方法
  3. 使用passedValidation()方法在成功验证后对请求对象进行任何操作
namespace App'Http'Requests;
use App'...'Language;
class UpdateLanguageRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            // here goes your rules, f.e.:
            'language' => ['max:255']
        ];
    }

    protected function passedValidation()
    {
        $this->replace(['language' => Language::iso2ToLegacy($this->language)]);
    }
}
  • UpdateLanguageRequest类代替Request
  • public function someControllerMethod(UpdateLanguageRequest $request){
        // the $request->language data was already modified at this point
    }
    

    *也许你想使用merge而不是replace方法,因为replace将替换请求中的所有其他数据,merge方法将仅替换特定值

    根据Alexander Ivashchenko上面的答案,这个解决方案对我有效:

    <?php
    namespace App'Http'Requests'User;
    class UserUpdateRequest extends UserRequest
    {
        /**
         * Get the validation rules that apply to the request.
         *
         * @return array
         */
        public function rules(): array
        {
            return [
                'name'=>'required|string',
                'email'=>'required|string|email',
                'password'=>'min:8'
            ];
        }
    }
    

    UserRequest类:

    <?php
    namespace App'Http'Requests'User;
    use Illuminate'Foundation'Http'FormRequest;
    use Illuminate'Support'Facades'Hash;
    abstract class UserRequest extends FormRequest
    {
        /**
         * Determine if the user is authorized to make this request.
         *
         * @return bool
         */
        public function authorize(): bool
        {
            return true;
        }
        /**
         * Handle a passed validation attempt.
         *
         * @return void
         */
        protected function passedValidation()
        {
            if ($this->has('password')) {
                $this->merge(
                    ['password' => Hash::make($this->input('password'))]
                );
            }
        }
        public function validated(): array
        {
            if ($this->has('password')) {
                return array_merge(parent::validated(), ['password' => $this->input('password')]);
            }
            return parent::validated();
        }
    }
    

    我也重写了有效的方法。如果我们单独访问每个输入元素,他的答案是有效的,但是为了在下面的控制器中使用批量分配,我们需要重写validated

    ...
    public function update(UserUpdateRequest $request, User $user): JsonResource
    {
        $user->update($request->validated());
        ...
    }
    ...
    

    这是因为validated方法直接从Validator而不是Request获取数据。另一个可能的解决方案是使用DTO方法的自定义验证器,但对于上面的简单内容来说,这已经足够了。

    是否有可能以某种方式将代码分组,将在之后执行请求验证成功,但在控制器动作之前开始了吗?

    您可以使用中间件作为验证器,例如:

    namespace App'Http'Middleware;
    use Closure;
    use Illuminate'Http'JsonResponse;
    class InputValidator
    {
        public function handle($request, Closure $next, $fullyQualifiedNameOfModel)
        {
            $model = app($fullyQualifiedNameOfModel);
            $validator = app('validator')->make($request->input(), $model->rules($request));
            if ($validator->fails()) {
                return $this->response($request, $validator->errors());
            }
            return $next($request);
        }
        protected function response($request, $errors)
        {
            if($request->ajax()) {
                return new JsonResponse($errors, 422);
            }
            return redirect()->back()->withErrors($errors)->withInput();
        }
    }
    

    App'Http'Kernel.php类的$routeMiddleware末尾添加以下条目:

    'validator' => 'App'Http'Middleware'InputValidator'
    

    Eloquent Model中添加rules方法,例如,app'Product.php是模型,rules方法声明如下:

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules('Illuminate'Http'Request $request)
    {
        return [
            'title' => 'required|unique:products,title,'.$request->route()->parameter('id'),
            'slug' => 'required|unique:products,slug,'.$request->route()->parameter('id'),
        ];
    }
    

    这样声明路由:

    $router->get('create', [
        'uses' => 'ProductController@create',
        'as' => 'Product.create',
        'permission' => 'manage_tag',
        'middleware' => 'validator:App'Product' // Fully qualified model name
    ]);
    

    您可以使用数组添加更多的middleware,例如:

    'middleware' => ['auth', 'validator:App'Product']
    

    这是一种使用单个middleware替换FormRequest的方法。我使用这个middlewaremodel name作为参数,使用单个middleware而不是每个控制器的单个FormRequest类来验证我的所有模型。

    这里,validatormiddleware, App'Product是模型名,我将其作为参数传递,并从middleware中验证该模型。

    根据你的问题,控制器内部的代码将只在输入验证通过后执行,否则redirect/ajax response将完成。出于特定的原因,您可以创建一个特定的middleware。这只是一个可以在您的情况下使用的想法IMO,我的意思是您可以添加代码来替换验证通过后特定middleware中的输入。

    使用merge代替replace

    $iso = $request->merge('language');
    $legacy = Language::iso2ToLegacy($iso);
    $request->merge(['language' => $legacy]);