-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Description
Symfony version(s) affected: 3.4.41
I haven't tested this on 4.x or 5.x, but looking at the code I believe this problem exists in these versions also.
Description
When an @Assert\Valid
constraint is placed on a getter for an association, and that association is null
, the exception Cannot create metadata for non-objects. Got: "NULL"
is thrown in trying to validate the object. If the constraint is placed on the property instead of the getter, then it works fine.
When the assertion is placed on the getter, the constraint uses a LazyProperty:
https://github.com/symfony/validator/blob/3.4/Validator/RecursiveContextualValidator.php#L540-L546
When validating, there is a check if the value is null, but when the value is a LazyProperty it always return an instance of LazyProperty even if the value is null:
https://github.com/symfony/validator/blob/3.4/Validator/RecursiveContextualValidator.php#L659-L661
A little later there is a check to see if the value is a LazyProperty and to get the actual value. I think this piece of code needs to come before the above check to see if the value is null:
https://github.com/symfony/validator/blob/3.4/Validator/RecursiveContextualValidator.php#L681-L683
How to reproduce
// ParentEntity.php
use Symfony\Component\Validator\Constraints as Assert;
class ParentEntity
{
private $id;
private $childEntity;
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): ParentEntity
{
$this->id = $id;
return $this;
}
/**
* @Assert\Valid()
*/
public function getChildEntity(): ?ChildEntity
{
return $this->childEntity;
}
public function setChildEntity(ChildEntity $childEntity = null): ParentEntity
{
$this->childEntity = $childEntity;
return $this;
}
}
// ChildEntity.php
class ChildEntity
{
private $id;
private $parentEntity;
public function getId(): int
{
return $this->id;
}
public function setId(int $id): ChildEntity
{
$this->id = $id;
return $this;
}
public function getParentEntity(): ?ParentEntity
{
return $this->parentEntity;
}
public function setParentEntity(ParentEntity $parentEntity = null): ChildEntity
{
$this->parentEntity = $parentEntity;
return $this;
}
}
// in controller
public function testAction(ValidatorInterface $validator)
{
$parent = new ParentEntity();
$validator->validate($parent);
}
Possible Solution
This piece of code from https://github.com/symfony/validator/blob/3.4/Validator/RecursiveContextualValidator.php#L681-L683:
if ($value instanceof LazyProperty) {
$value = $value->getPropertyValue();
}
Needs to happen before https://github.com/symfony/validator/blob/3.4/Validator/RecursiveContextualValidator.php#L659-L661. So the end result would be this:
if ($value instanceof LazyProperty) {
$value = $value->getPropertyValue();
}
if (null === $value) {
return;
}