Skip to content

[Form] CollectionType 'keep_as_list' Option with Doctrine Collections #57430

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
chris-k-k opened this issue Jun 17, 2024 · 10 comments
Closed

[Form] CollectionType 'keep_as_list' Option with Doctrine Collections #57430

chris-k-k opened this issue Jun 17, 2024 · 10 comments

Comments

@chris-k-k
Copy link

chris-k-k commented Jun 17, 2024

Symfony version(s) affected

7.1.1

Description

When mapping a CollectionType ('allow_delete' => true) to an entity property of type ArrayCollection, the following exception is thrown when setting the new 'keep_as_list' option true:

array_values(): Argument #1 ($array) must be of type array, Doctrine\ORM\PersistentCollection given

How to reproduce

Map a CollectionType to an ArrayCollection with 'keep_as_list' => true.

ExampleEntity:

#[ORM\OneToMany(targetEntity: Dataset::class, mappedBy: 'ExampleEntity', orphanRemoval: true, cascade: ['persist'])]
private Collection $dataset_collection;

public function __construct()
{
    $this->dataset_collection = new ArrayCollection();
}

Form with CollectionType:

$builder
	->add('dataset_collection', CollectionType::class, [
		'entry_type' => DatasetFormType::class,
		'allow_add' => true,
		'allow_delete' => true,
		'by_reference' => false,
		'keep_as_list' => true,
	])
;

Possible Solution

Before the 'keep_as_list' option was introduced in Symfony 7.1. I used to re-index my collections manually - which still works for collections of type ArrayCollection, too.
(https://symfonycasts.com/screencast/collections/embedded-validation#fixing-collectiontype-validation-bug).

Not a solution, but a quick fix: Check the type of $data in the onSubmit method in ResizeFormListener.php and convert it into an array if it is an instance of ArrayCollection or PersistentCollection.

if ($this->keepAsList) {
	$formReindex = [];
	foreach ($form as $name => $child) {
		$formReindex[] = $child;
		$form->remove($name);
	}
	foreach ($formReindex as $index => $child) {
		$form->add($index, $this->type, array_replace([
			'property_path' => '['.$index.']',
		], $this->options));
	}
	
	if ($data instanceof ArrayCollection || $data instanceof PersistentCollection) {
		$data = $data->toArray();
	}

	$data = array_values($data);
}

EDIT: 'keep_as_list' also breaks embedded forms that contain unmapped file upload inputs. Model-, norm- and viewData return empty string/null instead of an UploadedFile object.

Additional Context

No response

@GityaMan
Copy link

GityaMan commented Nov 7, 2024

EDIT: 'keep_as_list' also breaks embedded forms that contain unmapped file upload inputs. Model-, norm- and viewData return empty string/null instead of an UploadedFile object.

Hello. I also encountered unexpected behavior of the new option:
symfony/form/Extension/Core/EventListener/ResizeFormListener.php:159 removes $form and then re-adds it, but does not re-populate the data. So the data that was submitted inside the collection is lost when rendering the submitted form

@whataboutpereira
Copy link

Happened on the same just now with CollectionType with 'delete_empty' => true, 'keep_as_list' => true,.

If form submission is successful, then data on the entity is updated properly with a list.

However if submission fails and submitted data has an empty input you will end up with all empty inputs for the collection in the updated form.

@xabbuh
Copy link
Member

xabbuh commented Nov 8, 2024

Can anyone of create a small example application that allows to reproduce the behaviour you observe?

@whataboutpereira
Copy link

Can anyone of create a small example application that allows to reproduce the behaviour you observe?

Will have to think about that.

Right now it seems adding $form->setData($data); at the end of onSubmit() after $event->setData($data); in ResizeFormListener.php fixes it.

@whataboutpereira
Copy link

Can anyone of create a small example application that allows to reproduce the behaviour you observe?

https://github.com/whataboutpereira/symfony-form-keep-as-list-issue

Latest commit built on the symfony skeleton app.

If you enter anything in the three collection fields, the data is wiped in the refreshed form when keep_as_list in on.

The aforementioned adding $form->setData($data); at the end of onSubmit() after $event->setData($data); in ResizeFormListener.php fixes it.

@whataboutpereira
Copy link

Now that I look at it more closely, $form->setData($data) should be at the end of the $this->keepAsList block.

@whataboutpereira
Copy link

Or even set the data right when adding new children.

Unfortunately I'm not knowledgeable enough as to which is the best path. :)

                $form->add($index, $this->type, array_replace([
                    'property_path' => '[' . $index . ']',
+                    'data' => $child->getData(),
                ], $this->options));

@whataboutpereira
Copy link

whataboutpereira commented Nov 11, 2024

Now I see both $form->setData($data) and 'data' => $child->getData() are wrong, because submitted data will be overwritten with initial data set with 'data' in options. This fares better:

            foreach ($formReindex as $index => $child) {
                $form->add($index, $this->type, array_replace([
                    'property_path' => '[' . $index . ']',
                ],
-                $this->options));
+                $this->options, ['data' => $child->getData()]));
            }

@whataboutpereira
Copy link

Description

When mapping a CollectionType ('allow_delete' => true) to an entity property of type ArrayCollection, the following exception is thrown when setting the new 'keep_as_list' option true:

array_values(): Argument #1 ($array) must be of type array, Doctrine\ORM\PersistentCollection given

I now saw this one as well when working with an ArrayCollection.

The tests in src\Symfony\Component\Form\Tests\Extension\Validator\Type\FormTypeValidatorExtensionTest.php seem to operate against an array of objects, it doesn't test authors as ArrayCollection so doesn't catch this issue.

So there seem to be at least two separate issues with keep_as_list:

  1. keep_as_list gives an exception when the collection is an ArrayCollection.
  2. Elements in a plain array lose their values when re-displaying the form with submitted data.

@Trismegiste
Copy link

Trismegiste commented Mar 1, 2025

More info here

I've just encountered a related problem with the keep_as_list option of CollectionType.
When there are constraints on fields (in the form, not in the model) in the embedded form in a CollectionType, those constraints are simply ignored. They don't even bubble at the root of the form.

If I switch the keep_as_list to false, the constraints are working like before.

Seems to me this useful features is not fully functional. As someone mentioned above, I'm accustomed to reindex myself so a fix is not critical.

Sorry I wish I could provide a link to my project but sorry, it's currently private on github because it's a mess

PS : I don't use an ArrayCollection nor Doctrine, it's just a plain normal PHP array

xabbuh added a commit that referenced this issue Jun 10, 2025
…eCat)

This PR was merged into the 7.2 branch.

Discussion
----------

[Form] Fix `keep_as_list` when data is not an array

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix part of #57430
| License       | MIT

The `CollectionType` handles not only arrays but also `ArrayAccess&Traversable`. However when setting its `keep_as_list` option, `array_values` would then be called and crash.

To avoid this, this PR reindexes the data by getting its keys and unsetting them. This is because unsetting in a loop can produce counter-intuitive results; e.g. [`ArrayIterator` would skip indexes](https://www.php.net/manual/en/arrayiterator.offsetunset.php).

Note that #57430 mentions another issue that would be fixed by #59910.

Commits
-------

6c964e7 [Form] Fix `keep_as_list` when data is not an array
xabbuh added a commit that referenced this issue Jun 11, 2025
… collection type is enabled (kells)

This PR was merged into the 7.2 branch.

Discussion
----------

[Form] Keep submitted values when `keep_as_list` option of collection type is enabled

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #57430
| License       | MIT

When the `keep_as_list` option of `CollectionType` is true, its entries lose their values when re-displaying the form with submitted data.

This fix only concerns the 2nd point of the [linked issue](#57430 (comment)).

Commits
-------

bac56de [Form] Keep submitted values when keep_as_list option of collection type is enabled
@xabbuh xabbuh closed this as completed Jun 11, 2025
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

6 participants