Skip to content

Validator bug when @Assert\Valid is used on a getter that returns NULL #37430

@patrick-vandy

Description

@patrick-vandy

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;
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions