Skip to content

[Validator ]AbstractComparisonValidator propertyPath and NULL #29831

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
sgehrig opened this issue Jan 10, 2019 · 13 comments
Closed

[Validator ]AbstractComparisonValidator propertyPath and NULL #29831

sgehrig opened this issue Jan 10, 2019 · 13 comments

Comments

@sgehrig
Copy link
Contributor

sgehrig commented Jan 10, 2019

Symfony version(s) affected: 4.2.2

Description
Not sure whether this is a bug or the intended behaviour - but at least it seems to be inconsistent with my understanding on how null values are handled in the validator component.

Given two ?\DateTimeImmutable properties (nullable) in a class to define a timespan where NULL reflects unbounded, I'd like to use two comparison constraints to ensure that date 1 is always less than or equal to date 2. The AbstractComparisonValidator however only deals with NULL on the owning side of the constraint and does not handle NULL on the propertyPath constraint leading to validation errors with the LessThanOrEqual because the date 1 value (a \DateTimeImmutable) is not less than or equal to NULL. The GreaterThanOrEqual constraint is no problem - by coincidence - because a \DateTimeImmutable in date 2 is always greater than NULL in date 1.

How to reproduce

use Symfony\Component\Validator\Constraints as Assert;

class UpdateValidity
{
    /** 
     * @Assert\LessThanOrEqual(propertyPath="validUntil")
     *
     * @var \DateTimeImmutable|null
     */
    public $validFrom;

    /**
     * @Assert\GreaterThanOrEqual(propertyPath="validFrom")
     *
     * @var \DateTimeImmutable|null
     */
    public $validUntil;
}

$u = new UpdateValidity();
$u->validFrom = new \DateTimeImmutable('2019-01-01');
$u->validUntil = new \DateTimeImmutable('2019-12-31');
$validator->validate($u); // OK - validation succeeds

$u->validFrom = new \DateTimeImmutable('2020-01-01');
$validator->validate($u); // OK - validation fails

$u->validFrom = null;
$validator->validate($u); // OK - validation succeeds (by coincidence)

$u->validFrom = new \DateTimeImmutable('2019-01-01');
$u->validUntil = null;
$validator->validate($u); // Not OK - validation fails but should succeed in my opinion

Possible Solution

// vendor/symfony/validator/Constraints/AbstractComparisonValidator.php:50:62
if ($path = $constraint->propertyPath) {
    if (null === $object = $this->context->getObject()) {
        return;
    }

    try {
        $comparedValue = $this->getPropertyAccessor()->getValue($object, $path);
    } catch (NoSuchPropertyException $e) {
        throw new ConstraintDefinitionException(sprintf('Invalid property path "%s" provided to "%s" constraint: %s', $path, \get_class($constraint), $e->getMessage()), 0, $e);
    }
    // <new>
    if (null === $comparedValue) {
        return;
    }
    // </new>
} else {
    $comparedValue = $constraint->value;
}

Additional context
If required one could add a NotBlank or NotNull to the property to ensure that it must not be NULL.

@xabbuh
Copy link
Member

xabbuh commented Jan 10, 2019

Is this the same as #29719?

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 10, 2019

No. #29719 is about obtaining the parent object (either getObject() or getRoot()). This is about the value returned from the propertyPath.

@xabbuh
Copy link
Member

xabbuh commented Jan 10, 2019

Can you try out #29839?

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 11, 2019

@xabbuh: Should it be

return null === $value2 || $value1 <= $value2;

instead of

return null !== $value2 && $value1 <= $value2;

for LessThanOrEqualValidator (and accordingly for all other validators affected)?
Behause compareValues() will return false if $value2 is null but should return true.

@xabbuh
Copy link
Member

xabbuh commented Jan 11, 2019

Why would you expect it to return true? null means there is no value. I think implicitly assuming 0 in this case does not look right.

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 11, 2019

I'd assume that, if the value returned by propertyPath is null, the validator will just ignore the comparison. Just as the validator ignores the comparison if the owning side value (that's where the constraint is attached to) is null.

That's why my initial proposal was to handle this generally in the AbstractComparisonValidator (lines 50 to 62).

@xabbuh
Copy link
Member

xabbuh commented Jan 11, 2019

IMO that would be unexpected behaviour for the Equal/NotEqual and IdenticalTo/NotIdenticalTo constraints.

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 11, 2019

That's true. What do you think would be the expected behaviour for the (greater-than/less-than)/-or-equal validators? Would you follow my assumption?

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 23, 2019

A workaround this issue can be using the Expression constraint like that:

use Symfony\Component\Validator\Constraints as Assert;

class UpdateValidity
{
    /** 
     * @Assert\Expression(
     *      expression="value === null or this.validUntil === null or value <= this.validUntil",
     *      message="This value should be less than or equal to valid until."
     * )
     *
     * @var \DateTimeImmutable|null
     */
    public $validFrom;

    /**
     * @Assert\Expression(
     *      expression="value === null or this.validFrom === null or value >= this.validFrom",
     *      message="This value should be greater than or equal to valid from."
     * )
     *
     * @var \DateTimeImmutable|null
     */
    public $validUntil;
}

Just in case someone runs into the same problem.

@xabbuh
Copy link
Member

xabbuh commented Jan 28, 2019

That's tricky, I think there are good reasons to expect one or the other. I have no real preference.

@sgehrig
Copy link
Contributor Author

sgehrig commented Jan 28, 2019

Adding a property to allow defining the NULL behaviour would perhaps be the best option then. Especially because this would be backwards compatible. But that's a feature request then, right?

@xabbuh
Copy link
Member

xabbuh commented Jan 28, 2019

yes, that's not something for a bugfix

xabbuh added a commit that referenced this issue Dec 16, 2019
…aths (xabbuh)

This PR was merged into the 3.4 branch.

Discussion
----------

[Validator] fix comparisons with null values at property paths

| Q             | A
| ------------- | ---
| Branch?       | 3.4
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #29831
| License       | MIT
| Doc PR        |

Commits
-------

ffa5db7 fix comparisons with null values at property paths
@sgehrig
Copy link
Contributor Author

sgehrig commented Apr 3, 2020

As #29839 has been merged already, I assume this bug can be marked as "fixed". Right?

@xabbuh xabbuh closed this as completed Apr 3, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants