Skip to content

[Validator] When Constraint only works with public class properties #49367

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
ariCristina opened this issue Feb 14, 2023 · 3 comments
Closed

Comments

@ariCristina
Copy link

ariCristina commented Feb 14, 2023

Symfony version(s) affected

6.2.2

Description

When trying to use the When constraint and it seems to be working only with public class properties; if the property is declared protected or private, it throws the error Cannot access protected property App\Model\ExampleModel::$mainProperty

How to reproduce

In a model class, let's say App\Model\ExampleModel::$mainProperty, we declare 2 properties, where property protected $secondaryProperty should depend on protected $mainProperty. Boit properties have public getters and setters.

Not working

// Has your website accepted online payments before?
    #[
        Assert\Choice([0, 1])
    ]
    protected ?int $mainProperty = 1;

    #[Assert\When(
        expression: 'this.mainProperty == 1',
        constraints: [
            new Assert\NotBlank(message: "validator.form.error")
        ],
    )]
    protected ?string $secondaryProperty;

Partially working - not throwing error anymore, but does not evaluate the conditional expression properly, meaning id does not throw the constraint validation message; form behaves like it does not have an error.

// Has your website accepted online payments before?
    #[
        Assert\Choice([0, 1])
    ]
    public ?int $mainProperty = 1;

    #[Assert\When(
        expression: 'this.mainProperty == 1',
        constraints: [
            new Assert\NotBlank(message: "validator.form.error")
        ],
    )]
    protected ?string $secondaryProperty;

Working - only when the expression is set explicitly to TRUE; now, the constraint's message it is present when trying to submit the form

// Has your website accepted online payments before?
    #[
        Assert\Choice([0, 1])
    ]
    protected ?int $mainProperty = 1;

    #[Assert\When(
        expression: 'true',
        constraints: [
            new Assert\NotBlank(message: "validator.form.error")
        ],
    )]
    protected ?string $secondaryProperty;

###Info - both properties have public getters and setters

    /**
     * @return int|null
     */
    public function getMainProperty(): ?int
    {
        return $this->mainProperty;
    }

    /**
     * @param bool|null $mainProperty
     *
     * @return ExampleModel
     */
    public function setMainProperty(?bool $mainProperty): ExampleModel
    {
        $this->mainProperty = (int)$mainProperty;

        return $this;
    }

    /**
     * @return string|null
     */
    public function getSecondaryProperty(): ?string
    {
        return $this->secondaryProperty;
    }

    /**
     * @param string|null $secondaryProperty
     *
     * @return ExampleModel
     */
    public function setSecondaryProperty(?string $secondaryProperty): ExampleModel
    {
        $this->secondaryProperty = $secondaryProperty;

        return $this;
    }

Possible Solution

No response

Additional Context

image

Where $previousOnlinePaymentsAccepted is $mainProperty and $acquirer is $secondaryProperty

Error:
Cannot access protected property App\Model\WebsiteDetails\WebsiteDetailsModel::$previousOnlinePaymentsAccepted

  at vendor/symfony/expression-language/Node/GetAttrNode.php:92
  at Symfony\Component\ExpressionLanguage\Node\GetAttrNode->evaluate(array('constant' => array('compiler' => object(Closure), 'evaluator' => object(Closure))), array('value' => null, 'this' => object(WebsiteDetailsModel)))
     (vendor/symfony/expression-language/ExpressionLanguage.php:59)
  at Symfony\Component\ExpressionLanguage\ExpressionLanguage->evaluate('this.previousOnlinePaymentsAccepted', array('value' => null, 'this' => object(WebsiteDetailsModel)))
     (vendor/symfony/validator/Constraints/WhenValidator.php:37)
  at Symfony\Component\Validator\Constraints\WhenValidator->validate(null, object(When))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:743)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateInGroup(null, '00000000000005cd0000000000000000:App\\Model\\WebsiteDetails\\WebsiteDetailsModel:acquirer', object(PropertyMetadata), 'Default', object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:588)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateGenericNode(null, object(WebsiteDetailsModel), '00000000000005cd0000000000000000:App\\Model\\WebsiteDetails\\WebsiteDetailsModel:acquirer', object(PropertyMetadata), 'data.acquirer', array('Default'), null, 1, object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:502)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateClassNode(object(WebsiteDetailsModel), '00000000000005cd0000000000000000', object(ClassMetadata), 'data', array('Default'), null, 1, object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:299)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateObject(object(WebsiteDetailsModel), 'data', array('Default'), 1, object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:133)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validate(object(WebsiteDetailsModel), null, array('Default'))
     (vendor/symfony/form/Extension/Validator/Constraints/FormValidator.php:105)
  at Symfony\Component\Form\Extension\Validator\Constraints\FormValidator->validate(object(Form), object(Form))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:743)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateInGroup(object(Form), '00000000000006cf0000000000000000', object(ClassMetadata), 'Default', object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:474)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateClassNode(object(Form), '00000000000006cf0000000000000000', object(ClassMetadata), '', array('Default'), null, 1, object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:299)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateObject(object(Form), '', array('Default'), 1, object(ExecutionContext))
     (vendor/symfony/validator/Validator/RecursiveContextualValidator.php:133)
  at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validate(object(Form), null, array('Default'))
     (vendor/symfony/validator/Validator/RecursiveValidator.php:82)
  at Symfony\Component\Validator\Validator\RecursiveValidator->validate(object(Form), null, null)
     (vendor/symfony/validator/Validator/TraceableValidator.php:58)
  at Symfony\Component\Validator\Validator\TraceableValidator->validate(object(Form))
     (vendor/symfony/form/Extension/Validator/EventListener/ValidationListener.php:46)
  at Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener->validateForm(object(PostSubmitEvent), 'form.post_submit', object(EventDispatcher))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:206)
  at Symfony\Component\EventDispatcher\EventDispatcher->callListeners(array(object(Closure), object(Closure), object(Closure)), 'form.post_submit', object(PostSubmitEvent))
     (vendor/symfony/event-dispatcher/EventDispatcher.php:56)
  at Symfony\Component\EventDispatcher\EventDispatcher->dispatch(object(PostSubmitEvent), 'form.post_submit')
     (vendor/symfony/event-dispatcher/ImmutableEventDispatcher.php:30)
  at Symfony\Component\EventDispatcher\ImmutableEventDispatcher->dispatch(object(PostSubmitEvent), 'form.post_submit')
     (vendor/symfony/form/Form.php:591)
  at Symfony\Component\Form\Form->submit(array())
@stof
Copy link
Member

stof commented Feb 14, 2023

the Symfony ExpressionLanguage does not have the same magic than Twig for the . operator (because its goal is to not have any runtime when compiling to an expression). So .mainProperty really means accessing the property mainProperty, not also trying to use its getter when it is not public. Use .getMainProperty() in your expression and it will work.

@ariCristina
Copy link
Author

ariCristina commented Feb 14, 2023

the Symfony ExpressionLanguage does not have the same magic than Twig for the . operator (because its goal is to not have any runtime when compiling to an expression). So .mainProperty really means accessing the property mainProperty, not also trying to use its getter when it is not public. Use .getMainProperty() in your expression and it will work.

I tried with expression: 'this.getPreviousOnlinePaymentsAccepted() == 1', and, indeed, does not throw the exception from the first example; in this case, the docs should be updated, because they did not mirror this behavior: https://symfony.com/doc/current/reference/constraints/When.html

This also fixes my problem, the only reason it was not working was a coding logic when initialize the form; this.getPreviousOnlinePaymentsAccepted() == 0' returned true.

Much appreciated; I was not very familiar with the Symfony ExpressionLanguage.

@stof
Copy link
Member

stof commented Feb 14, 2023

I opened symfony/symfony-docs#17905 to update the documentation with a working example.

I'm closing this issue here as there is no bug in Symfony itself.

@stof stof closed this as completed Feb 14, 2023
OskarStark added a commit to symfony/symfony-docs that referenced this issue Feb 14, 2023
This PR was merged into the 6.2 branch.

Discussion
----------

Fix the example for the When constraint

When evaluating the expression, the ExpressionLanguage does not have access to private properties of the object, as it runs from its outside. And contrary to Twig, ExpressionLanguage does not have the magic `.` operator that tries to find a getter when it cannot use the property.

See symfony/symfony#49367 for a support request caused by this bad example.

Commits
-------

7ad1447 Fix the example for the When constraint
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