处理用户数据的 Symfony 集合违规

Handling Symfony Collection violations for user data

我正在编写一个 API,它将接收 JSON 字符串,解析它并 return 请求的数据。我正在使用 Symfony 的验证组件来执行此操作,但我在验证数组时遇到了一些问题。

例如,如果我有这个数据:

{
    "format": {
        "type": "foo"
    }
}

然后我可以很容易地用 PHP 代码验证这个:

$constraint = new Assert\Collection(array(
    "fields" => array(
      "format" => new Assert\Collection(array(
        "fields" => array(
          "type" => new Assert\Choice(["foo", "bar"])
        )
      ))
    )
  ));

  $violations = $validator->validate($data, $constraint);

  foreach ($violations as $v) {
      echo $v->getMessage();
  }

如果type既不是foo,也不是bar,那么我就违规了。即使 type 是像 DateTime 对象这样的异国情调的东西,我仍然会遇到违规行为。简单!

但是如果我将我的数据设置为:

{
    "format": "uh oh"
}

然后我没有收到违规(因为 Assert\Collection 需要一个数组),而是收到一条令人讨厌的 PHP 消息:

Fatal error: Uncaught Symfony\Component\Validator\Exception\UnexpectedTypeException: Expected argument of type "array or Traversable and ArrayAccess", "string" given [..]

如果有一种巧妙的方法来处理这样的事情,而不需要手动尝试/捕获和处理错误,也不必加倍验证(例如,一个验证来检查 format 是否是一个数组,然后另一个验证来检查 type 是否有效)?

完整代码的要点在这里:https://gist.github.com/Grayda/fec0ed7487641645304dee668f2163ac

我正在使用 Symfony 4

据我所知,所有内置验证器在期望数组但收到其他内容时都会抛出异常,因此您必须编写自己的验证器。您可以创建一个自定义验证器,首先检查该字段是否为数组,然后才运行其余的验证器。

约束条件:

namespace App\Validation;

use Symfony\Component\Validator\Constraints\Composite;

/**
 * @Annotation
 * @Target({"PROPERTY", "METHOD", "ANNOTATION"})
 */
class IfArray extends Composite
{
    public $message = 'This field should be an array.';

    public $constraints = array();

    public function getDefaultOption()
    {
        return 'constraints';
    }

    public function getRequiredOptions()
    {
        return array('constraints');
    }

    protected function getCompositeOption()
    {
        return 'constraints';
    }
}

验证器:

namespace App\Validation;

use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;

class IfArrayValidator extends ConstraintValidator
{
    /**
     * {@inheritdoc}
     */
    public function validate($value, Constraint $constraint)
    {
        if (!$constraint instanceof IfArray) {
            throw new UnexpectedTypeException($constraint, __NAMESPACE__.'\IfArray');
        }

        if (null === $value) {
            return;
        }

        if (!is_array($value) && !$value instanceof \Traversable) {
            $this->context->buildViolation($constraint->message)
                ->addViolation();

            return;
        }

        $context = $this->context;

        $validator = $context->getValidator()->inContext($context);
        $validator->validate($value, $constraint->constraints);
    }
}

请注意,这与 All 约束非常相似,主要区别在于如果 !is_array($value) && !$value instanceof \Traversabletrue,代码将添加违规而不是抛出异常.

现在可以像这样使用新约束:

$constraint = new Assert\Collection(array(
        "fields" => array(
            "format" => new IfArray(array(
                "constraints" => new Assert\Collection(array(
                    "fields" => array(
                        "type" => new Assert\Choice(["foo", "bar"])
                    )
                ))
            )),
        )
    ));