5

所以我有一个 Laravel 应用程序,它有许多控制器来处理应用程序的各个方面。

现在每个控制器都有各种方法。大多数方法都定义了验证规则,例如:

$validationArray = [
    'id'=>'required|integer',
    'status'=>'required|string'
];
$validator = Validator::make($request->all(),$validationArray);
if ($validator->fails()){
    return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);
}

现在是以下行:

return Response::json(['response'=>implode(', ',$validator->messages()->all())],422);

实际上返回验证规则的任何错误。

我的问题是:有没有办法以编程方式获取所有可能的错误消息?

当然,做到这一点的一种方法是逐个规则地绕过并手动列出一个列表,但是有数百种方法分散在各种控制器中。

因此,如果有人能指出我以更简单的方式获取所有错误消息的方向,将不胜感激。

先感谢您!

更新

因此,为了进一步清除,我需要一个所有可能错误的列表,就像上面的代码一样,列表如下:

['id is required', 'id must be an integer', 'status is required', 'status must be an string']

更新 2

请记住,有数百种方法,而且我不想更改方法的最终响应,但有某种外部脚本可以帮助我在不干扰控制器的情况下获取错误消息。

4

7 回答 7

5

为此,您必须扩展Validator类并编写一个方法,该方法将迭代所有规则并显式添加错误消息,就好像它们失败了一样。

首先,创建一个新文件app\Http\Custom\Validator.php

<?php

namespace App\Http\Custom;

use Illuminate\Contracts\Validation\Rule as RuleContract;
use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Validation\Validator as BaseValidator;

class Validator extends BaseValidator {

  /** @var MessageBag */
  protected $errorMessages;

  /** @var array */
  protected $hasExplicitFileErrorMessage;

  protected $explicitFileRules = [
    'File', 'Image', 'Mimes', 'Mimetypes', 'Dimensions',
  ];

  function availableErrors()
  {
    $this->errorMessages = new MessageBag();
    $this->hasExplicitFileErrorMessage = [];

    foreach($this->rules as $attribute => $rules) {
      $attribute = str_replace('\.', '->', $attribute);
      foreach($rules as $rule) {
        [$rule, $parameters] = ValidationRuleParser::parse($rule);

        if($rule == '') {
          continue;
        }
        if(($keys = $this->getExplicitKeys($attribute)) &&
          $this->dependsOnOtherFields($rule)) {
          $parameters = $this->replaceAsterisksInParameters($parameters, $keys);
        }
        // explicitly add "failed to upload" error
        if($this->hasRule($attribute, $this->explicitFileRules) && !in_array($attribute, $this->hasExplicitFileErrorMessage)) {
          $this->addFailureMessage($attribute, 'uploaded', []);
          $this->hasExplicitFileErrorMessage[] = $attribute;
        }

        if($rule instanceof RuleContract) {
          $messages = $rule->message() ? (array)$rule->message() : [get_class($rule)];
          foreach($messages as $message) {
            $this->addFailureMessage($attribute, get_class($rule), [], $message);
          }
        } else {
          $this->addFailureMessage($attribute, $rule, $parameters);
        }
      }
    }

    return $this->errorMessages->all();
  }

  function addFailureMessage($attribute, $rule, $parameters = [], $rawMessage = null)
  {
    $this->errorMessages->add($attribute, $this->makeReplacements(
      $rawMessage ?? $this->getMessage($attribute, $rule), $attribute, $rule, $parameters
    ));
  }

  // we have to override this method since file-type errors depends on data value rather than rule type
  protected function getAttributeType($attribute)
  {
    if($this->hasRule($attribute, $this->explicitFileRules)) {
      return 'file';
    }
    return parent::getAttributeType($attribute);
  }
}

接下来,让我们在 Validation factory 中注册这个类:

<?php

namespace App\Providers;

use App\Http\Custom\Validator; // <-- our custom validator
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider {

  public function boot()
  {
    app('validator')->resolver(function ($translator, $data, $rules, $messages) {
      return new Validator($translator, $data, $rules, $messages);
    });
  }

}

就这样。让我们测试一下:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;

class HomeController extends Controller {

  function index(Request $request)
  {
    $rules = [
      'id'      => 'required|int|between:2,10',
      'status'  => 'required_with:nonexisting|string|email',
      'avatar'  => 'required|file|mimes:png|max:1000',
      'company' => 'required_without:id|unique:companies,id'
    ];

    $validator = Validator::make([], $rules);

    dump($validator->availableErrors());
  }

}
array:13 [▼
  0 => "The id field is required."
  1 => "The id must be an integer."
  2 => "The id must be between 2 and 10."
  3 => "The status field is required when nonexisting is present."
  4 => "The status must be a string."
  5 => "The status must be a valid email address."
  6 => "The avatar failed to upload."
  7 => "The avatar field is required."
  8 => "The avatar must be a file."
  9 => "The avatar must be a file of type: png."
  10 => "The avatar may not be greater than 1000 kilobytes."
  11 => "The company field is required when id is not present."
  12 => "The company has already been taken."
]
于 2019-10-16T08:52:47.813 回答
1

第 1 点:就像我在问题下的评论中提到的那样,您想要实现的目标可能会以更简单的方式完成。

第 2 点:由于您不想更改已经编写好的代码,->messages()那么您可以执行以下操作。我将列出这些步骤并提供示例代码。

  • 我们需要重写 Laravel 的验证器、(验证)工厂和 ValidationService 提供者类。
  • App\Services文件夹中,您可以创建两个类ValidatorValidationFactory
  • App\Providers创建一个类ValidationServiceProvider
  • 进入config/app.php文件并providers替换 Illuminate\Validation\ValidationServiceProvider::classApp\Providers\ValidationServiceProvider::class

Validator类看起来像这样:

namespace App\Services;

use Illuminate\Support\MessageBag;
use Illuminate\Validation\ValidationRuleParser;
use Illuminate\Contracts\Translation\Translator;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Illuminate\Contracts\Validation\Rule as RuleContract;

class Validator extends \Illuminate\Validation\Validator
{
    /**
     * @var MessageBag $all_messages
     */
    protected $all_messages;

    public function __construct(Translator $translator, array $data, array $rules, array $messages = [], array $customAttributes = [])
    {
        parent::__construct($translator, $data, $rules, $messages, $customAttributes);

        $this->all_messages = new MessageBag;
        $this->getAllFormattedMessages();
    }

    public function makeAllRulesMessages($attribute, $rule, $parameters)
    {
        $this->all_messages->add($attribute, $this->makeReplacements(
            $this->getMessage($attribute, $rule), $attribute, $rule, $parameters
        ));
    }

    public function messages(bool $validated_rules_messages = false)
    {
        return $validated_rules_messages
            ? $this->validatedMessages()
            : $this->all_messages;
    }

    /**
     * This is here in case the true validated messages are needed
     *
     * @return MessageBag
     */
    public function validatedMessages()
    {
        return parent::messages();
    }

    public function getAllFormattedMessages()
    {
        // We'll spin through each rule and add all messages to it.
        foreach ($this->rules as $attribute => $rules) {
            $attribute = str_replace('\.', '->', $attribute);

            foreach ($rules as $rule) {
                // First we will get the correct keys for the given attribute in case the field is nested in
                // an array. Then we determine if the given rule accepts other field names as parameters.
                // If so, we will replace any asterisks found in the parameters with the correct keys.

                [$rule, $parameters] = ValidationRuleParser::parse($rule);

                if (($keys = $this->getExplicitKeys($attribute)) &&
                    $this->dependsOnOtherFields($rule)) {
                    $parameters = $this->replaceAsterisksInParameters($parameters, $keys);
                }

                $value = $this->getValue($attribute);

                if ($value instanceof UploadedFile && $this->hasRule($attribute, array_merge($this->fileRules, $this->implicitRules))
                ) {
                    $this->makeAllRulesMessages($attribute, 'uploaded', []);
                } elseif ($rule instanceof RuleContract) {
                     $this->makeCustomRuleMessage($attribute, $rule);
                } else {
                    $this->makeAllRulesMessages($attribute, $rule, $parameters);
                }

            }
        }
    }

    /**
     * @param $attribute
     * @param \Illuminate\Contracts\Validation\Rule  $rule $rule
     */
    public function makeCustomRuleMessage($attribute, $rule)
    {
        $this->failedRules[$attribute][get_class($rule)] = [];

        $messages = (array)$rule->message();

        foreach ($messages as $message) {
            $this->all_messages->add($attribute, $this->makeReplacements(
                $message, $attribute, get_class($rule), []
            ));
        }
    }
}

这个类总结起来做一件事,把所有通过规则的消息都放到$all_messages类的属性中。它扩展并允许基本验证类运行,并简单地覆盖messages()方法以使所有收集的规则可供使用。

ValidationFactory覆盖Illuminate\Validation\Factory,它看起来像这样:

namespace App\Services;

use Illuminate\Validation\Factory;

class ValidationFactory extends Factory
{
    /**
     * Resolve a new Validator instance.
     *
     * @param  array  $data
     * @param  array  $rules
     * @param  array  $messages
     * @param  array  $customAttributes
     * @return \Illuminate\Validation\Validator
     */
    protected function resolve(array $data, array $rules, array $messages, array $customAttributes)
    {
        if (is_null($this->resolver)) {
            return new \App\Services\Validator($this->translator, $data, $rules, $messages, $customAttributes);
        }

        return call_user_func($this->resolver, $this->translator, $data, $rules, $messages, $customAttributes);
    }
}

这个类只做一件事,resolve()通过使用我们自定义类的实例来覆盖这个类中的方法\App\Services\Validator

ValidationServiceProvider扩展Illuminate\Validation\ValidationServiceProvider和覆盖registerValidationFactory()方法,它看起来像这样:

namespace App\Providers;

use App\Services\ValidationFactory;
use Illuminate\Validation\ValidationServiceProvider as BaseValidationServiceProvider;

class ValidationServiceProvider extends BaseValidationServiceProvider
{

    protected function registerValidationFactory()
    {
        $this->app->singleton('validator', function ($app) {
            $validator = new ValidationFactory($app['translator'], $app);

            // The validation presence verifier is responsible for determining the existence of
            // values in a given data collection which is typically a relational database or
            // other persistent data stores. It is used to check for "uniqueness" as well.
            if (isset($app['db'], $app['validation.presence'])) {
                $validator->setPresenceVerifier($app['validation.presence']);
            }

            return $validator;
        });
    }
}

上面的类所做的也是指示提供者App\Services\ValidationFactory在应用程序需要时使用我们的。

我们完成了。即使我们的验证规则之一失败,也会显示所有验证消息。

注意事项

为了实现这一点,我们需要进行大量更改和覆盖。除了非常关键之外,这可能表明应用程序的设计看起来有问题。

Laravel 验证实现可能会在未来的版本中更改,因此可能会成为维护这些更改的问题。

我无法确定覆盖 Laravel 的默认验证实现是否会发生其他副作用,或者是否所有规则都返回正确的消息。

通常您只想将失败的验证消息返回给用户,而不是所有可能的失败。

于 2019-10-20T00:41:31.970 回答
1

它不漂亮,但这是我的镜头:

$validationArray = [
    'id'=>'required|integer',
    'status'=>'required|string'
];

$validator = Validator::make($request->all(), $validationArray);
if ($validator->fails()) {
    $messages = [];
    $invalid_fields = array_keys($validator->messages()->toArray());
    $rules = $v->getRules();

    foreach($invalid_fields as $invalid_field) {
        foreach($rules[$invalid_field] as $rule) {
            if(str_contains($rule, ':') {
                // complex rules that have parameters (min, between, size, format) 
                // are more difficult to work with. I haven't figured out how to do them yet
                // but you should get the idea. 
                continue;
            } else {
                $messages[] = str_replace(':attribute', $invalid_field, $validator->getTranslator()->get("validation.$rule"));
            }
        }
    }

    return Response::json(['response' => implode(', ', $messages)], 422);
}
于 2019-10-17T03:04:23.223 回答
0

我认为函数failed()(获取失败的验证规则)或errors()(获取验证器的消息容器)可能会对您有所帮助。如果没有,请访问https://laravel.com/api/5.8/Illuminate/Validation/Validator.html,希望您能找到所需的功能。

于 2019-10-10T07:39:33.563 回答
0

我认为您正在寻找一种自定义错误消息的方法。如果是这种情况,那么答案是这样的:

$messages = [
    'id.required' => 'id is required',
    'id.integer' => 'id must be an integer',
    'status.required' => 'status is required',
    'status.string'=> 'status must be an string'
];

$validationArray = [
    'id'=>'required|integer',
    'status'=>'required|string'
];

$validator = Validator::make($request->all(),$validationArray, $messages);

您可以在这里找到更多信息。

我希望这是您正在寻找的,我的回答对您有所帮助:)

于 2019-10-15T15:17:47.963 回答
0

基于 Laravel 表单验证程序,您可以通过以下方式编写语句:

 $validationArray = [
   'id'=>'required|integer',
   'status'=>'required|string'
 ];

 $validator = Validator::make($request->all(),$validationArray);

 if ($validator->fails()){
  return Response::json(['response'=> validator->errors())],422);
 }

其中errors()方法将所有错误作为关联数组返回,其中消息将相应地与字段名称相关联,这就是您获取错误的方式。

于 2019-10-22T06:18:24.280 回答
0

我认为,您应该实施https://laravel.com/docs/5.8/validation#form-request-validation

这将帮助您实施投注者。

于 2019-10-10T07:26:15.330 回答