-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[FORM] Prevent forms from extending itself as a parent #24387
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
Thanks for the PR, but I wouldn't qualify this as a bug. As an enhancement, so a new feature, this must target 4.1, where you'll use the form type FQCN rather than the name btw :) |
I had this issue on 3.4 with FOSUserBundle when I copy-pasted some code from the docs (https://symfony.com/doc/master/bundles/FOSUserBundle/overriding_forms.html), but my class name was the same as one of the core bundle class names (I changed the class strings to |
Sure, I understand that. But that's not an issue within the form component itself, so it cannot be part of a patch release. This PR is a DX improvement. We're used to qualify those as features, and it lands on new minor versions. |
I agree with @ogizanagi's statement, this is not a bug fix but a DX improvement. It allows users to better use the component. Maybe it needs a doc entry with a warning when you do that ? |
While this changes a behavior, I think the feature freeze period is fine for this kind of changes, where no new feature is really introduced (no new public anything). |
Preventing an infinite loop also could legitimately qualify as a bug fix to me, so it's not obvious it must go on 4.1. |
So talking a bit with @nicolas-grekas, and as it does not only enhance DX but prevents an infinite loop, I agree this must be qualified as a bug fix. |
|
||
public function getParent() | ||
{ | ||
return new FormWithSameParentTypeAsObject(); |
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.
Please, use return new self();
to make fabbot happy again ;)
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 did consider changing this initially when I saw the failure, but I'm just concerned it would make the test less intuitive
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.
Why would it look less intuitive to you? Would be even more explicit to me :)
Could you do the change?
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.
except Fabbot must be happy
What about more complex infinite loops ? |
6ce04fe
to
513b238
Compare
static $checkedTypes = array(); | ||
|
||
$searchKey = array_search($type->getName(), $checkedTypes); | ||
$checkedTypes[] = $type->getName(); |
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.
would putting the name as key work instead?
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 the name is the key, then you won't get the proper dependency path when throwing an error,
E.G if you have the dependencies foo -> bar -> foo
then you can only have the values foo > bar
in the array that is displayed in the message, where we want to show foo > bar > foo
(So we want to be able to have duplicate values in the array)
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.
Is it possible to have duplicate values and not have a circular ref? If not, then shouldn't this be just the terminal condition? (ie the last > foo
comes from the current call, not from the checkedTypes stack?)
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.
You're right, any duplicate value would immediately be the cause of the circular ref. I have updated this
@@ -97,13 +98,33 @@ public function getType($name) | |||
*/ | |||
private function resolveAndAddType(FormTypeInterface $type) | |||
{ | |||
static $checkedTypes = array(); |
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 need to be static, or can it be a regular private property?
also, what about replacing the $checkedTypes = array();
reset by a (method-wide) finally?
That'd be more robust I think
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.
Initially when I had it as a private property, it didn't work and still ended in an infinite loop, but I must have been doing something wrong since it's working fine now :)
Also this still needs to support PHP 5.3, so we can't use finally
$parentType = $type->getParent(); | ||
|
||
if ($parentType instanceof FormTypeInterface) { | ||
if ($parentType->getName() === $type->getName()) { | ||
$this->checkedTypes = array(); |
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 we should still use a try/catch emulating finally, so that the code can recover from any unexpected exception
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.
(with one minor comment)
(please mind fabbot also) |
Sorry, but this does not qualify as a bug fix. It's a nice DX improvement for sure, but it does not fix anything (as having an infinite loop is a dev error, nothing wrong with the code). I would merge this in 3.4 instead as a new feature (which by the way is we added some similar circular refs checks in the past). |
a867fde
to
73cc6d6
Compare
Rebased against 3.4 |
unset($this->checkedTypes[$fqcn]); | ||
} | ||
} finally { | ||
$this->checkedTypes = array(); |
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 this is buggy: there should be only one try/finally, it should start right after $this->checkedTypes[$fqcn] = true;
, and should only unset($this->checkedTypes[$fqcn]);
(doing $this->checkedTypes = array()
could be possible, but only on exceptions - but is not required if we unset properly)
$this->checkedTypes[$fqcn] = true; | ||
|
||
try { | ||
if ($parentType === $fqcn) { |
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.
one last thing sorry: this "if" can be moved before L117, isn't it?
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 wondering if this is even necessary anymore? Without it, you would still get the circular ref error. Or is it better to be explicit in checking the same type as the parent?
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.
let's remove it then you're right
Thank you @pierredup. |
… (pierredup) This PR was squashed before being merged into the 3.4 branch (closes #24387). Discussion ---------- [FORM] Prevent forms from extending itself as a parent | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | N/A | License | MIT | Doc PR | N/A If a form type defines itself as a parent, it results in an endless recursive loop in the form registry, so throw an exception instead. Commits ------- 2ee6fd5 [FORM] Prevent forms from extending itself as a parent
$typeExtensions = array_merge( | ||
if (isset($this->checkedTypes[$fqcn])) { | ||
$types = implode(' > ', array_merge(array_keys($this->checkedTypes), array($fqcn))); | ||
throw new LogicException(sprintf('Circular reference detected for form "%s" (%s).', $fqcn, $types)); |
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.
it is detected for a form type, not for a form.
…ies check (pierredup) This PR was squashed before being merged into the 3.4 branch (closes #24504). Discussion ---------- [Form] Fix error message in circular reference dependencies check | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | no | Fixed tickets | | License | MIT | Doc PR | Fix comments from PR #24387 Commits ------- 48eb43d [Form] Fix error message in circular reference dependencies check
If a form type defines itself as a parent, it results in an endless recursive loop in the form registry, so throw an exception instead.