Reputation: 324
I have a simple entity with three fields: an autogenerated ID, a string and an integer. I have set constraints for the latter two, and the constraint corresponding to the integer field works perfectly. If I send an integer which is not in the range, or should I send anything but an integer, then an error is returned.
However that does not happen with the string field. If I send an integer the form validates correctly. Why is that happening?
The code I am using for testing is:
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 Entity
<?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;
}
}
Upvotes: 2
Views: 4702
Reputation: 18416
The issue occurs during a typecast of the Form's submit method here: Symfony\Component\Form\Form::submit()
if (false === $submittedData) {
$submittedData = null;
} elseif (is_scalar($submittedData)) {
$submittedData = (string) $submittedData;
}
The reason is to keep compatibility with a standard HTTP request always submitting values as string and to allow Symfony's Form\Types, to cast the values to the desired type.
Keep in mind the parent Form is compound and each of the fields are children Form elements, which causes the typecasting to be performed.
So for example:
$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());
Result:
array:1 [▼
"a" => "1"
]
true
In this case TextType
does not cast the value to anything, but the submit had already typecast the value to a string.
However it would fail validation when a new Assert\Type(['type' => 'int'])
constraint was used, and the field type was not Form\IntegerType
.
Whereas a Range constraint only tests for a numeric value that meets the min and max, which works on numeric strings as well.
Example: https://3v4l.org/ErQrC
$min = 1;
$max = 10;
$a = "5";
var_dump($a < $max && $a > $min); //true
Versus: https://3v4l.org/8D3N0
$min = 1;
$max = 10;
$a = "c";
var_dump($a < $max && $a > $min); //false
To resolve the issue you can create your own constraint expression, assuming you do not want a numeric value.
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);
}
}
Upvotes: 2