Laravel 5.5 - 同时验证多个表单请求

10

这个问题在之前的 Laravel 版本中已经被问过了,但还没有得到答复,链接在此

我有一个包含三个不同的表单请求验证的 HTML 表单。我已经能够做到这一点,但问题是,表单验证是逐个进行的,而不是同时进行的。

如果第一个表单请求引发了验证错误,则会将表单返回到 view 中,因此无法对其余两个表单进行评估,从而无法向用户显示正确的验证错误。

我想要的是:同时使用三个表单验证请求的规则验证表单

控制器:

public function store(TransportationRequest $request1, PurchaseRequest $request2, SaleRequest $request3)
    {
        //do actions here
    }

我已经尝试逐一继承表单请求,但没有成功。

编辑:

更具体地说:

我有三个单独的表单,用于单独操作 购买运输销售,分别使用 PurchaseRequestTransportationRequestSaleRequest 进行评估。

但是有一个特殊情况,一个单一的表单处理 购买运输销售我想使用组合三个表单请求规则来验证表单,因为我不想再写相同的验证规则了。

这就是:

注意:单独表单和组合表单中的字段是相同的。

谢谢..


为什么你需要3个单独的请求?请求背后的思想是它映射到一个传入的 HTTP 请求,其中只有一个。也就是说,你可以(我认为 - 我自己没有尝试过)创建另一个请求对象,将你的 3 个请求作为参数传递给它的构造函数。Laravel 的 IoC 容器应该能够将每个请求项传递给构造函数,然后你可以使用每个请求实例来构建一组单一的规则和消息等。 - Jonathon
我的问题是,您如何同时发布3个表单,还是将单个表单拆分为3个部分的不同请求?对于原始问题的答案是,您需要手动创建验证器并在每个验证器上运行passes()方法,然后按照此处所述收集错误。 - apokryfos
你能举个需要单独的固定请求类的例子吗?你提供的问题在多个请求参数的情况下没有意义。我从未遇到过一个固定请求不足的情况。 - user320487
@jonathon,我也尝试了那种方式,但在将第一个请求传递给构造函数后,它也会返回到视图。我不熟悉Laravel的IoC容器,也无法在文档(5.5)中找到它。 - iamab.in
@apokryfos的问题已更新。我想将验证逻辑分离到表单请求中,这就是为什么我不使用手动验证器的原因。 - iamab.in
@btl问题已更新,附带样例解释。 - iamab.in
8个回答

16

当验证失败时,FormRequest会抛出一个Illuminate\Validation\ValidationException异常,该异常具有redirectTo方法,然后异常Handler执行重定向

您可以通过在控制器中手动运行表单请求并在try/catch块中捕获错误并组合错误包来实现所需的行为,或者如果您需要通过Laravel将它们注入到控制器中,则需要添加自己的异常处理程序,以捕获所有错误,将它们组合起来,然后在最终表单请求运行后进行重定向。

然而,值得注意的是,这两种方法都不太好:它们很麻烦,并且会给您带来比解决问题更多的问题。如果您想编写可维护的应用程序,应尽可能遵循Laravel的做法。

FormRequest存在于验证表单的情况下,因此每个表单都应该有一个FormRequest,如果您希望从不同的规则集中组成FormRequest,则应在FormRequest中完成,例如:

  1. Define your Form Request for your form php artisan make:request StoreMultipleForm
  2. From the rules method on StoreMultipleForm fetch the rules for each of the other Form Requests and then return them together, e.g:

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        $formRequests = [
          TransportationRequest::class,
          PurchaseRequest::class,
          SaleRequest::class
        ];
    
        $rules = [];
    
        foreach ($formRequests as $source) {
          $rules = array_merge(
            $rules,
            (new $source)->rules()
          );
        }
    
        return $rules;
    }
    
  3. Use the new composed Form Request in your controller, e.g:

    public function store(StoreMultipleForm $request)
    {
        // Do actions here.
    }
    
这种方法的优点是它是自包含的,符合“一个表单一个Form Request”的期望,不需要更改您正在组合的Form Requests,并且如果您需要添加针对此表单的其他规则,则可以这样做而无需创建另一个Form Request。

2
你写了一个很好的答案,但为了保持完整性,我认为你需要在每个函数上调用authorize函数。 - Sherif Tarek

3

我会创建包含每个FormRequest - 购买、运输和销售的规则的特征。在特定的FormRequest中使用这个特征,当你需要所有的规则时,可以在组合的FormRequest中使用这三个特征,并合并规则数组。


1
当在同一个表单请求类中使用多个具有rules()方法的trait时,您将会遇到模糊的方法声明。 - Alika Matthew

2
如果我理解正确,您有3个表单,每个表单都有自己的表单请求来处理其各自的验证。您还有另一个表单,将这3个表单组合在一起,并且您不想通过重写那些验证规则来重复自己。
在这种情况下,我仍然建议使用单个表单请求,但尝试组合每个单独请求的规则。例如,您可以使用静态方法在3个单独的表单请求上定义规则,并让每个单独的请求调用自己的静态方法来获取它们:
class TransportationRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

class PurchaseRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

class SaleRequest extends FormRequest
{
    public static function getRules()
    {
        return []; // Return rules for this request
    }

    public function rules()
    {
        return static::getRules();
    }
}

然后,您需要让您的合并请求合并这三个集合:

class CombinedRequest extends FormRequest
{
    public function rules()
    {
        return array_merge(
            TransportationRequest::getRules(),
            SaleRequest::getRules(),
            PurchaseRequest::getRules()
        );
    }
}

然后,您可以在控制器方法中使用单个CombinedRequest。当然,如果您不喜欢静态方法的方式,在您的组合请求rules方法中,您可以只new每个单独的请求并调用每个请求的rules方法并合并结果。

class CombinedRequest extends FormRequest
{
    public function rules()
    {
        $transportation = (new TransportationRequest())->rules();
        $sale = (new SaleRequest())->rules();
        $purchase = (new PurchaseRequest())->rules();

        return array_merge(
            $transportation,
            $sales,
            $purchase
        );
    }
}

2
您可以逐个手动执行请求类:
public function store()
{
    $isValid = true;
    try{
        app(TransportationRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    try{
        app(PurchaseRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    try{
        app(SaleRequest::class);
    } catch (Illuminate\Validation\ValidationException $ex){
        $isValid = false ;
    }
    if (!$isValid){
        throw $ex;
    }
}

如果其中一个验证失败,用户将被重定向回先前的页面并显示错误信息。

2

事实证明,如果您解析请求,验证将会启动。
其中一个注意事项是,并不是所有的验证对象都会同时启动,但根据您的用例,这可能更简单。

    public function store()
    {
        // request()->replace([ some fields to be validated ]);
        resolve(TransportationRequest::class);
        resolve(PurchaseRequest::class);
        resolve(SaleRequest::class);
        // request is valid
    }

1
可以尝试在每个“resolve”上添加try{..} catch(Illuminate\Validation\ValidationException $ex){...},并将“resolve”替换为“app”,以改进这个答案 :) - Yevgeniy Afanasyev

1
您可以将所有规则连接起来并手动验证:
$allRules = (new TransportationRequest)->rules() + (new PurchaseRequest)->rules() + (new SaleRequest)->rules();
Validator::make($request->all(), $allRules)->validate();

1

0

我最近遇到了这个问题,并且像这样解决了它:

public function rules()
{
    $request1 = RequestOne::createFrom($this);
    $request2 = RequestTwo::createFrom($this);

    return array_merge(
        $request1->rules(),
        $request2->rules()
    );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接