Обработка нарушений Symfony Collection для пользовательских данных

Я пишу API, который примет строку JSON, проанализирует ее и вернет запрошенные данные. Для этого я использую компонент проверки 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/fec0ed7487641645304dee668/ >

Я использую Symfony 4


person Grayda    schedule 10.02.2018    source источник


Ответы (1)


Насколько я понимаю, все встроенные валидаторы выдают исключение, когда они ожидают массив, но получают что-то еще, поэтому вам придется написать свой собственный валидатор. Вы можете создать собственный валидатор, который сначала проверяет, является ли поле массивом, и только затем запускает остальные валидаторы.

Ограничение:

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 \Traversable равно true, код добавит нарушение вместо того, чтобы генерировать исключение.

Новое ограничение теперь можно использовать так:

$constraint = new Assert\Collection(array(
        "fields" => array(
            "format" => new IfArray(array(
                "constraints" => new Assert\Collection(array(
                    "fields" => array(
                        "type" => new Assert\Choice(["foo", "bar"])
                    )
                ))
            )),
        )
    ));
person Size43    schedule 10.02.2018
comment
Очень легко понять, и образец работает отлично. К сожалению, мы должны это сделать, но рад, что это может быть незаменимым решением. Спасибо! - person Grayda; 10.02.2018