处理用户数据的 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 \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"])
)
))
)),
)
));
我正在编写一个 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 \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"])
)
))
)),
)
));