-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Validator] Access container array in Expression/Context #23134
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
Conversation
@@ -46,6 +47,10 @@ public function validate($value, Constraint $constraint) | |||
$variables['value'] = $value; | |||
$variables['this'] = $this->context->getObject(); | |||
|
|||
if (strlen($constraint->dataPath) > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is correct but following the code base style I suggest null !== $constraint->dataPath
to avoid function call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be null !== $constraint->dataPath && '' !== $constraint->dataPath
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about allowing the $dataPath
option to also be a PropertyPath
instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm i don't like this because than it can be a string
or instanceof PropertyPath
. If we keep in mind that we want to use later on scalar type hints for version 4.0, we would have a problem.
@@ -46,6 +47,10 @@ public function validate($value, Constraint $constraint) | |||
$variables['value'] = $value; | |||
$variables['this'] = $this->context->getObject(); | |||
|
|||
if (strlen($constraint->dataPath) > 0) { | |||
$variables['this'] = $this->getPropertyAccessor()->getValue($this->context, $constraint->dataPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if $variables['this']
may be overridden I suggest move $variables['this'] = $this->context->getObject();
to else
statement?
@@ -46,6 +47,10 @@ public function validate($value, Constraint $constraint) | |||
$variables['value'] = $value; | |||
$variables['this'] = $this->context->getObject(); | |||
|
|||
if (strlen($constraint->dataPath) > 0) { | |||
$variables['this'] = $this->getPropertyAccessor()->getValue($this->context, $constraint->dataPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the other hand, if Symfony deciders decides to make it configurable I think we could start the path from the root
i.e.:
$this->getPropertyAccessor()->getValue($this->context->getRoot(), $constraint->dataPath);
else, I suggest pass the root
value to this
directly if it's null
i.e::
if (null === $variables['this'] = $this->context->getObject()) {
$variables['this'] = $this->context->getRoot();
}
thus, you don't have to worry about the "dataPath", you know the data linked to the root, therefore you just have to access them:
new Expression(array('expression' => 'value <= this["data"]["dateEnd"]'));
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea, to pass the root
value directly to this
if it's null. No dataPath
and so easier to use. I will wait to change it because I would like to know how the deciders think about this.
This one could fix this #22403 ticket too ;) |
@Engerim I changed the base branch to 3.4. Can you please rebase on branch 3.4 and push again on your side? |
@nicolas-grekas sorry for the late response, i rebased and pushed again. |
@@ -235,4 +235,17 @@ public function testExpressionLanguageUsage() | |||
|
|||
$this->assertTrue($used, 'Failed asserting that custom ExpressionLanguage instance is used.'); | |||
} | |||
|
|||
public function testExpressionLanguageUsageWithCustomDataPath() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this name make sense? Maybe just testExpressionWithCustomDataPath
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I orientated me on the naming from the test function above with name testExpressionLanguageUsage
. But I can change it for better understanding
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I saw that. But the previous test actually tests that passing in a custom ExpressionLanguage
instance works. Hence the name 😄
* add dataPath Property to extract "this" in a different way from the context
* update changelog
* change string check
* change test method name to a better name
private function getPropertyAccessor() | ||
{ | ||
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) { | ||
throw new RuntimeException('Unable to use expressions with data path as the Symfony PropertyAccess component is not installed.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should not throw this exception in the constraint to allow to detect this mistake earlier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course we could throw it there. But than we need to check there if dataPath
is set because only then we check if the PropertyAccess is avaibale
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that would be fine.
@xabbuh how should we move on here? 4.1? |
@@ -11,6 +11,7 @@ CHANGELOG | |||
the `Url::CHECK_DNS_TYPE_*` constants values and will throw an exception in Symfony 4.0 | |||
* added min/max amount of pixels check to `Image` constraint via `minPixels` and `maxPixels` | |||
* added a new "propertyPath" option to comparison constraints in order to get the value to compare from an array or object | |||
* added a `dataPath` option to the `Expression` constraint to allow an other way to get "this" from the context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"this" should be enclosed with backticks
|
||
private function getPropertyAccessor() | ||
{ | ||
if (!class_exists('Symfony\Component\PropertyAccess\PropertyAccess')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should use the class
constant instead
@@ -46,6 +47,10 @@ public function validate($value, Constraint $constraint) | |||
$variables['value'] = $value; | |||
$variables['this'] = $this->context->getObject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to improve readability I would make this line an else
branch of the if
below
For me, if @Engerim could address my last comments very soon this could be merged into 3.4. Otherwise, I would vote for 4.1 too. |
I work on this at the moment |
* moved check if property access component is installed to constraint * improve readability
* cs
the failing test has nothing to do with the changes i made |
@@ -44,7 +45,12 @@ public function validate($value, Constraint $constraint) | |||
|
|||
$variables = array(); | |||
$variables['value'] = $value; | |||
$variables['this'] = $this->context->getObject(); | |||
|
|||
if (null !== $constraint->dataPath && '' !== $constraint->dataPath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if ($constraint->dataPath) {
should be better
|
||
private function getPropertyAccessor() | ||
{ | ||
return PropertyAccess::createPropertyAccessor(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the exception in src/Symfony/Component/Validator/Constraints/Expression.php should be moved here IMHO:
since the property is public, the check in the constructor is very fragile, only here would be robust
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather make the property private
then. Catching these mistakes when the constraint is created allows to spot issues earlier which improves DX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made the property private
and added a magic __get
like it was done in the File
Constraint.
@@ -65,4 +71,9 @@ private function getExpressionLanguage() | |||
|
|||
return $this->expressionLanguage; | |||
} | |||
|
|||
private function getPropertyAccessor() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO we don't need this method anymore and can inline the createPropertyAccessor()
call instead.
* made dataPath private
* made dataPath protected so test is not failing
I don't know why windows is failing. This is not a problem from my changes |
$variables['this'] = $this->context->getObject(); | ||
|
||
if ($constraint->dataPath) { | ||
$variables['this'] = PropertyAccess::createPropertyAccessor()->getValue($this->context, $constraint->dataPath); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still I think we can get rid of dataPath
altogether and set the root context directly for this case. Even, if the root is a form instance then we could set the form data.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would you do that if the data is not available at the time you configure the constraint (which by the way is almost always the case when not using PHP to set up your constraints)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure to understand your question :( but in short this is what I meant:
if (null === $variables['this'] = $this->context->getObject()) {
if (($root = $this->context->getRoot()) instanceof FormInterface) {
$variables['this'] = $root->getData();
} else {
$variables['this'] = $root;
}
}
This will work with the example of the PR description and even for cases where there is data_class
and the expression is setting over form fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But this would force the Validator component to know something about the Form component. I don't think that's a good idea. There may also be other similar issues (not related to Symfony) which we couldn't fix here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, then just:
$variables['this'] = $this->context->getObject() ?: $this->context->getRoot();
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO there is no need to configure a dataPath
if we have already a data root that we know, why not start from it directly?
Moving to 4.1. |
I'm 👎 for this solution, because if this option is used in conjunction with form which have data binded, Also, this is really "fix" for #22403, rather than #12315 For more proper fix of #12315 see #25529 Also #25529 can be used with a trick to get out of this situation in user space. Something like this: $obj = new \stdClass();
$builder->addEventListener(FormEvents::POST_SET_DATA, function(FormEvent $f) use ($obj) {
$obj->form = $f->getForm();
});
...
new Expression([
'expression' => 'obj.form["dateEnd"] >= obj.form["dateStart"]',
'variables' => ['obj' => $obj],
]); Similar solution could be built into Form component. |
…sion validator (ostrolucky) This PR was merged into the 4.1-dev branch. Discussion ---------- [Validator] Add option to pass custom values to Expression validator | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - I needed this in a Form. I had no way to pass things from `$options` into Expression validator. Maybe can aid in #23134 Commits ------- ba0565e [Validator] Add option to pass custom values to Expression validator
Closing as #25529 was merged. |
This solves an old issues with arrays and the expression validator. Below is a melody script which throws an exception because "this" is null and not an array like expected.
This PR adds a
dataPath
property to theExpression
constraint, which allows the validator to retrieve the "this" value in an other way from the context.I'm not sure about the name "dataPath", so i'm open for a better naming.
Documentation PR will be added later, if this PR is approved.