如何获取 Laravel 控制器方法的所有可能的错误消息

How to get all possible error messages of a Laravel Controller method

所以我有一个 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);

实际上 returns 验证规则有什么问题。

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

当然,一种方法是逐条绕过规则并手动制作列表,但是有数百种方法分散在各种控制器中。

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

提前致谢!

更新

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

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

更新 2

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

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

这将帮助您进行投注。

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

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

$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);

您可以找到更多信息 here

希望这就是您要找的,我的回答对您有所帮助:)

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

首先,创建一个新文件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);
  }
}

接下来,让我们在验证工厂中注册这个class:

<?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."
]

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

$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);
}

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

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

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

Validator class looks like so:

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), []
            ));
        }
    }
}

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

ValidationFactory overrides Illuminate\Validation\Factory and it looks like so:

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);
    }
}

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

ValidationServiceProvider extends Illuminate\Validation\ValidationServiceProvider and overrides registerValidationFactory() method and it looks like so:

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;
        });
    }
}

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

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

注意事项

In order to achieve this, we needed to make a lot of changes and overriding. Except really critical this may signal that something about the app's design looks wrong.

Laravel validation implementation may change in future release and therefore may become a problem maintaining these changes.

I cannot tell if there are other side effects that might happen for overriding Laravel's default validation implementation or if all the rules return the right messages.

Normally you only want to return failed validation messages to user rather than all the possible failures.

基于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() 方法 return 所有错误作为关联数组,其中消息将相应地与字段名称相关联,这就是您获取错误的方式.