字符串的 Symfony 表单类型约束验证整数值
Symfony Form Type constraint of string validates Integer value
我有一个包含三个字段的简单实体:一个自动生成的 ID、一个字符串和一个整数。后两者我都设置了约束条件,整型字段对应的约束条件完美。如果我发送一个不在范围内的整数,或者我应该发送除整数以外的任何内容,则会返回错误。
但是字符串字段不会发生这种情况。如果我发送一个整数,表单就会正确验证。为什么会这样?
我用来测试的代码是:
curl -H "Content-Type: application/json" -X POST -d '{"fieldOne": 9000, "fieldTwo": 5}' http://localhost:8000/foos
FooController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\FooBar;
use AppBundle\Form\FooBarType;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\View\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FooController extends FOSRestController
{
public function getFooAction()
{
$view = new View();
$data = array(
'foo' => 'bar'
);
$view->setStatusCode(Response::HTTP_OK);
$view->setData($data);
return $view;
}
public function postFooAction(Request $request)
{
$foobar = new FooBar();
$form = $this->createForm(FooBarType::class, $foobar);
$data = json_decode($request->getContent(), true);
$form->submit($data);
if ($form->isValid()) {
//$foobar = $form->getData();
$response = new Response('All good');
$response->setStatusCode(Response::HTTP_OK);
return $response;
}
return View::create($form, Response::HTTP_BAD_REQUEST);
}
}
FooBarType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class FooBarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fieldOne')
->add('fieldTwo')
;
}
}
FooBar 实体
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* FooBar
*
* @ORM\Table(name="foo_bar")
* @ORM\Entity(repositoryClass="AppBundle\Repository\FooBarRepository")
*/
class FooBar
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @Assert\Type("string")
* @ORM\Column(name="field_one", type="string", length=255)
*/
private $fieldOne;
/**
* @var int
*
* @Assert\Range(
* min = 1,
* max = 10,
* minMessage = "You must be at least {{ limit }}cm tall to enter",
* maxMessage = "You cannot be taller than {{ limit }}cm to enter"
* )
* @ORM\Column(name="field_two", type="int")
*/
private $fieldTwo;
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set fieldOne
*
* @param string $fieldOne
*
* @return FooBar
*/
public function setFieldOne($fieldOne)
{
$this->fieldOne = $fieldOne;
return $this;
}
/**
* Get fieldOne
*
* @return string
*/
public function getFieldOne()
{
return $this->fieldOne;
}
/**
* Set fieldTwo
*
* @param int $fieldTwo
*
* @return FooBar
*/
public function setFieldTwo($fieldTwo)
{
$this->fieldTwo = $fieldTwo;
return $this;
}
/**
* Get fieldOne
*
* @return string
*/
public function getFieldTwo()
{
return $this->fieldTwo;
}
}
此问题发生在表单提交方法的类型转换期间:Symfony\Component\Form\Form::submit()
if (false === $submittedData) {
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
}
原因是为了与始终以字符串形式提交值的标准 HTTP 请求保持兼容性,并允许 Symfony Form\Types 将值转换为所需的类型。
请记住,父表单是复合的,每个字段都是子表单元素,这会导致执行类型转换。
例如:
$builder->add('a', Form\TextType::class, [
'constraints' => [
new Assert\Type(['type' => 'string'])
]
]);
$form = $builder->getForm();
$form->submit(['a' => 1]);
dump($form->getData);
dump($form->isValid());
结果:
array:1 [▼
"a" => "1"
]
true
在这种情况下 TextType
不会将值转换为任何内容,但提交已经将值转换为字符串。
然而,当使用 new Assert\Type(['type' => 'int'])
约束且字段类型不是 Form\IntegerType
.
时,验证将失败
而范围约束仅测试满足最小值和最大值的数值,这也适用于数字字符串。
$min = 1;
$max = 10;
$a = "5";
var_dump($a < $max && $a > $min); //true
$min = 1;
$max = 10;
$a = "c";
var_dump($a < $max && $a > $min); //false
要解决此问题,您可以创建自己的 constraint expression,前提是您不需要数值。
class FooBar
{
//...
/**
* @var string
*
* @Assert\Type("string")
* @Assert\Expression("this.isNotNumeric()")
* @ORM\Column(name="field_one", type="string", length=255)
*/
private $fieldOne;
/**
* @return bool
*/
public function isNotNumeric()
{
return !is_numeric($this->fieldOne);
}
}
我有一个包含三个字段的简单实体:一个自动生成的 ID、一个字符串和一个整数。后两者我都设置了约束条件,整型字段对应的约束条件完美。如果我发送一个不在范围内的整数,或者我应该发送除整数以外的任何内容,则会返回错误。
但是字符串字段不会发生这种情况。如果我发送一个整数,表单就会正确验证。为什么会这样?
我用来测试的代码是:
curl -H "Content-Type: application/json" -X POST -d '{"fieldOne": 9000, "fieldTwo": 5}' http://localhost:8000/foos
FooController.php
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\FooBar;
use AppBundle\Form\FooBarType;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Request\ParamFetcher;
use FOS\RestBundle\View\View;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FooController extends FOSRestController
{
public function getFooAction()
{
$view = new View();
$data = array(
'foo' => 'bar'
);
$view->setStatusCode(Response::HTTP_OK);
$view->setData($data);
return $view;
}
public function postFooAction(Request $request)
{
$foobar = new FooBar();
$form = $this->createForm(FooBarType::class, $foobar);
$data = json_decode($request->getContent(), true);
$form->submit($data);
if ($form->isValid()) {
//$foobar = $form->getData();
$response = new Response('All good');
$response->setStatusCode(Response::HTTP_OK);
return $response;
}
return View::create($form, Response::HTTP_BAD_REQUEST);
}
}
FooBarType.php
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class FooBarType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fieldOne')
->add('fieldTwo')
;
}
}
FooBar 实体
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* FooBar
*
* @ORM\Table(name="foo_bar")
* @ORM\Entity(repositoryClass="AppBundle\Repository\FooBarRepository")
*/
class FooBar
{
/**
* @var int
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @Assert\Type("string")
* @ORM\Column(name="field_one", type="string", length=255)
*/
private $fieldOne;
/**
* @var int
*
* @Assert\Range(
* min = 1,
* max = 10,
* minMessage = "You must be at least {{ limit }}cm tall to enter",
* maxMessage = "You cannot be taller than {{ limit }}cm to enter"
* )
* @ORM\Column(name="field_two", type="int")
*/
private $fieldTwo;
/**
* Get id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* Set fieldOne
*
* @param string $fieldOne
*
* @return FooBar
*/
public function setFieldOne($fieldOne)
{
$this->fieldOne = $fieldOne;
return $this;
}
/**
* Get fieldOne
*
* @return string
*/
public function getFieldOne()
{
return $this->fieldOne;
}
/**
* Set fieldTwo
*
* @param int $fieldTwo
*
* @return FooBar
*/
public function setFieldTwo($fieldTwo)
{
$this->fieldTwo = $fieldTwo;
return $this;
}
/**
* Get fieldOne
*
* @return string
*/
public function getFieldTwo()
{
return $this->fieldTwo;
}
}
此问题发生在表单提交方法的类型转换期间:Symfony\Component\Form\Form::submit()
if (false === $submittedData) {
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
}
原因是为了与始终以字符串形式提交值的标准 HTTP 请求保持兼容性,并允许 Symfony Form\Types 将值转换为所需的类型。
请记住,父表单是复合的,每个字段都是子表单元素,这会导致执行类型转换。
例如:
$builder->add('a', Form\TextType::class, [
'constraints' => [
new Assert\Type(['type' => 'string'])
]
]);
$form = $builder->getForm();
$form->submit(['a' => 1]);
dump($form->getData);
dump($form->isValid());
结果:
array:1 [▼
"a" => "1"
]
true
在这种情况下 TextType
不会将值转换为任何内容,但提交已经将值转换为字符串。
然而,当使用 new Assert\Type(['type' => 'int'])
约束且字段类型不是 Form\IntegerType
.
而范围约束仅测试满足最小值和最大值的数值,这也适用于数字字符串。
$min = 1;
$max = 10;
$a = "5";
var_dump($a < $max && $a > $min); //true
$min = 1;
$max = 10;
$a = "c";
var_dump($a < $max && $a > $min); //false
要解决此问题,您可以创建自己的 constraint expression,前提是您不需要数值。
class FooBar
{
//...
/**
* @var string
*
* @Assert\Type("string")
* @Assert\Expression("this.isNotNumeric()")
* @ORM\Column(name="field_one", type="string", length=255)
*/
private $fieldOne;
/**
* @return bool
*/
public function isNotNumeric()
{
return !is_numeric($this->fieldOne);
}
}