Thanks to visit codestin.com
Credit goes to github.com

Skip to content

FormInterface is reported as \Traversable<string, FormInterface> but can return int as key. #46698

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
VincentLanglet opened this issue Jun 17, 2022 · 6 comments · Fixed by #46813

Comments

@VincentLanglet
Copy link
Contributor

VincentLanglet commented Jun 17, 2022

Symfony version(s) affected

5.0.0 and more

Description

Because the formBuilder use name as key, and because php is casting int-string like '0' to int, we sometimes ends with form name which is integer and this cannot be directly used by method like FormInterface::remove.

How to reproduce

The following code

foreach ($form as $name => $child) {
     $form->remove($name);
}

is not working when the form was build this way

$formBuilder->add('0', ...)

Because the name is casted to an int when used as an array key here:
https://github.com/symfony/symfony/blob/6.2/src/Symfony/Component/Form/FormBuilder.php#L71-L72

There are some situation in the Symfony code where the form names can be int and passed to method expecting string. One of them is the ChoiceType with expanded option.
https://github.com/symfony/form/blob/6.1/Extension/Core/Type/ChoiceType.php#L407-L419

So far this code is working because declare(strict_types=1); is not used.

But for my first example

foreach ($form as $name => $child) {
     $form->remove($name);
}

if it's written in the userland with strict_types, it won't work even if all the phpdoc make believe it will:

  • FormInterface is a \Traversable<string, FormInterface>
  • remove accepts string

Possible Solution

I see at least four solutions here:

  • Changing some of the typehint in https://github.com/symfony/symfony/blob/6.1/src/Symfony/Component/Form/FormInterface.php to accepts int|string (since it's a BC-break, this might wait for 7.0)
  • Changing the phpdoc to \Traversable<int|string, FormInterface> with some comments; at least static-analysis user will be warned.
  • Deprecating passing an integer-as-string as a form name ; this might be the smoother solution. Instead of $formBuilder->add('0') it should be $formBuilder->add('prefix_0'). And in SF 7.0, this will throw an exception.
  • Doing nothing

Additional Context

No response

@stof
Copy link
Member

stof commented Jun 17, 2022

Note that if you don't turn on strict types in your file, the key implicitly casted as int by PHP will also be implicitly casted to string by the argument typehint.

Deprecating numeric strings would break CollectionType for instance.

@VincentLanglet
Copy link
Contributor Author

Note that if you don't turn on strict types in your file, the key implicitly casted as int by PHP will also be implicitly casted to string by the argument typehint.

Yes, I agree ; the issue only exists with strict_types. But I'm not sure the answer should be "We recommend to every Symfony user to not use strict_types".

Deprecating numeric strings would break CollectionType for instance.

This is with this formType I had the issue when trying to reproduce a similar ResizeFormListener.
If one day Symfony decide to use strict_types, this file wouldn't work anymore https://github.com/symfony/symfony/blob/6.2/src/Symfony/Component/Form/Extension/Core/EventListener/ResizeFormListener.php#L68

@VincentLanglet
Copy link
Contributor Author

Any recommendation on this issue @nicolas-grekas or @xabbuh ?
I'm voluntary to contribute, but I don't know which solution you would prefer.

@derrabus
Copy link
Member

Option 5: Make sure integer keys are being cast back to string in our Traversable implementations?

@VincentLanglet
Copy link
Contributor Author

Option 5: Make sure integer keys are being cast back to string in our Traversable implementations?

Seems like a nice idea @derrabus.

We're using a OrderedHashMap for childrens

$this->children = new OrderedHashMap();

The getIterator method is returning an OrderedHashMapIterator
return new OrderedHashMapIterator($this->elements, $this->orderedKeys, $this->managedCursors);

And we could change the key method here:
public function key(): mixed
{
if (null === $this->key) {
return null;
}
$array = [$this->key => null];
return key($array);
}

Should I

  • cast the key to string ? Because it's only used for string keys so far and the class is internal.
  • create another iterator which extends this one ?

Also, which branch should I target

@derrabus
Copy link
Member

Dunno, like this?

public function getIterator(): \Traversable
{
    foreach ($this->children as $key => $value) {
        yield (string) $key => $value;
    }
}

which branch should I target

Good question. Maybe we should play it safe and target 6.2.

@fabpot fabpot closed this as completed Jul 3, 2022
fabpot added a commit that referenced this issue Jul 3, 2022
…incentLanglet)

This PR was squashed before being merged into the 6.2 branch.

Discussion
----------

[Form] Provide string keys when iterating on a form

| Q             | A
| ------------- | ---
| Branch?       | 6.2  as recommended by #46698 (comment)
| Bug fix?      | no (?)
| New feature?  | no
| Deprecations? | no
| Tickets       | Fix #46698
| License       | MIT
| Doc PR        | symfony/symfony-docs#... <!-- required for new features -->

cc @derrabus

Commits
-------

69de9a2 [Form] Provide string keys when iterating on a form
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants