Laravel 请求没有 return 修改后的请求验证失败

Laravel Request does not return the modified request on validation fail

我最近更新了我的控制器以在保存之前使用请求来验证数据,最初我在控制器路由中使用 $request->validate(),但我现在正处于一个我真的需要将其分离出来的阶段一个请求。

问题

在进行验证之前,我需要更改请求中的一些参数,我发现这可以使用 prepareForValidation() 方法来完成,并且在验证请求中的值期间效果很好已被更改。如果验证失败,我的问题就来了。我需要能够 return 我已经更改回视图的请求,目前,在重定向之后它似乎正在使用我之前的请求 运行 prepareForValidation() . (即 return 的标题为 'ABCDEFG' 而不是 'Changed The Title')。

在阅读了其他 SO posts 和 Laravel 论坛 posts 之后,看起来我需要覆盖 FormRequest::failedValidation() 方法(我已经完成,请参见下面的代码),但是我仍在努力寻找一种方法来传回我更改后的请求。我已尝试编辑 failedValidation() 方法,我在下方提供了详细信息。

期望与现实

预期

  1. 用户输入 'ABCDEFG' 作为标题并按保存。
  2. 请求中的标题(使用 prepareForValidation())更改为 'Changed The Title'。
  3. 验证失败,用户被重定向回创建页面。
  4. 标题字段的内容现在是“更改了标题”。

现实

  1. 用户输入 'ABCDEFG' 作为标题并按保存。
  2. 请求中的标题(使用 prepareForValidation())更改为 'Changed The Title'。
  3. 验证失败,用户被重定向回创建页面。
  4. 标题字段的内容显示“ABCDEFG”。

我试过的

正在将请求传递给 ValidationException class。

深入研究代码后,看起来 ValidationException 允许将响应作为参数传递。

    protected function failedValidation(Validator $validator)
    {
        throw (new ValidationException($validator, $this))
            ->errorBag($this->errorBag)
            ->redirectTo($this->getRedirectUrl());
    }

然而,这会导致 Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::addCookieToResponse 中的错误 Call to undefined method Symfony\Component\HttpFoundation\HeaderBag::setCookie()

将请求闪烁到 session

我的下一次尝试是将我的请求闪现到 session,这似乎不起作用,而不是我修改后的请求在 session,它看起来是请求在我 运行 prepareForValidation().

之前
    protected function failedValidation(Validator $validator)
    {
        $this->flash();

        throw (new ValidationException($validator))
            ->errorBag($this->errorBag)
            ->redirectTo($this->getRedirectUrl());
    }

返回响应而不是异常

我最后的尝试是 return 使用 withInput() 而不是异常的响应。

    protected function failedValidation(Validator $validator)
    {
        return redirect($this->getRedirectUrl())
        ->withErrors($validator)
        ->withInput();
    }

但是看起来代码继续进入 BlogPostController::store() 方法而不是重定向回视图。

在这一点上我没有想法,如果验证失败,我似乎无法将更改后的请求返回到视图!

其他注意事项

  1. 我几乎是一个 Laravel 新手,我有使用松散地基于 Laravel 的自定义框架的经验,但这是我的第一个 CMS 项目。
  2. 我完全理解我可能走错了路,也许有更好的方法来更改请求并在验证失败时将其传回?
  3. 我这样做的目的是什么?最主要的是活动复选框。默认情况下是勾选的(见下面的blade),如果用户取消勾选并保存,HTTP请求中不会传递active,因此active不存在Laravel 请求 object 并且当用户 return 返回时,激活的复选框在不应该的时候再次被选中。
  4. 那你为什么在你的例子中使用了title?我在我的 post 中使用 title 因为我认为它更容易看到我想要实现的目标。

非常感谢您的帮助,因为我目前已经花了好几个小时来解决这个问题。

相关代码

BlogPostController.php

<?php

namespace App\Http\Controllers;

use App\BlogPost;
use Illuminate\Http\Request;
use App\Http\Requests\StoreBlogPost;

class BlogPostController extends Controller
{

    /**
     * Run the auth middleware to make sure the user is authorised.
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('admin.blog-posts.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  StoreBlogPost    $request
     * @return \Illuminate\Http\Response
     */
    public function store(StoreBlogPost $request)
    {
        $blogPost = BlogPost::create($request->all());

        // Deal with the listing image upload if we have one.
        foreach ($request->input('listing_image', []) as $file) {
            $blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('listing_image');
        }

        // Deal with the main image upload if we have one.
        foreach ($request->input('main_image', []) as $file) {
            $blogPost->addMedia(storage_path(getenv('DROPZONE_TEMP_DIRECTORY') . $file))->toMediaCollection('main_image');
        }

        return redirect()->route('blog-posts.edit', $blogPost->id)
            ->with('success', 'The blog post was successfully created.');
    }
}

// Removed unrelated controller methods.

StoreBlogPost.php

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Validation\ValidationException;

class StoreBlogPost extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'title'         => 'required',
            'url'           => 'required',
            'description'   => 'required',
            'content'       => 'required',
        ];
    }

    /**
     * Get the error messages for the defined validation rules.
     *
     * @return array
     */
    public function messages()
    {
        return [
            'title.required'        => 'The Title is required',
            'url.required'          => 'The URL is required',
            'description.required'  => 'The Description is required',
            'content.required'      => 'The Content is required',
        ];
    }

    /**
     * Prepare the data for validation.
     *
     * @return void
     */
    protected function prepareForValidation()
    {
        $this->merge([
            'active' => $this->active ?? 0,
            'title' => 'Changed The Title',
        ]);
    }

    /**
     * @see FormRequest
     */
    protected function failedValidation(Validator $validator)
    {
        throw (new ValidationException($validator))
            ->errorBag($this->errorBag)
            ->redirectTo($this->getRedirectUrl());
    }
}

create.blade.php

@extends('admin.layouts.app')

@section('content')
<div class="edit">
    <form action="{{ route('blog-posts.store') }}" method="POST" enctype="multipart/form-data">
        @method('POST')
        @csrf

        <div class="container-fluid">
            <div class="row menu-bar">
                <div class="col">
                    <h1>Create a new Blog Post</h1>
                </div>
                <div class="col text-right">
                    <div class="btn-group" role="group" aria-label="Basic example">
                        <a href="{{ route('blog-posts.index') }}" class="btn btn-return">
                            <i class="fas fa-fw fa-chevron-left"></i>
                            Back
                        </a>

                        <button type="submit" class="btn btn-save">
                            <i class="fas fa-fw fa-save"></i>
                            Save
                        </button>
                    </div>
                </div>
            </div>
        </div>

        <div class="container-fluid">
            <div class="form-group row">
                <label for="content" class="col-12 col-xl-2 text-xl-right col-form-label">Active</label>
                <div class="col-12 col-xl-10">
                    <div class="custom-control custom-switch active-switch">
                      <input type="checkbox" name="active" value="1" id="active" class="custom-control-input" {{ old('active', '1') ? 'checked' : '' }}>
                      <label class="custom-control-label" for="active"></label>
                    </div>
                </div>
            </div>

            <div class="form-group row">
                <label for="title" class="col-12 col-xl-2 text-xl-right col-form-label required">Title</label>
                <div class="col-12 col-xl-10">
                    <input type="text" name="title" id="title" class="form-control" value="{{ old('title', '') }}">
                </div>
            </div>
        </div>
    </form>
</div>
@endsection

我遇到了同样的问题,这是我在研究 laravel 代码后找到的解决方案。

似乎 Laravel 为 FormRequest 创建了一个不同的对象,所以你可以这样做。

protected function failedValidation(Validator $validator)
{
    // Merge the modified inputs to the global request.
    request()->merge($this->input());

    parent::failedValidation($validator);
}