Skip to content

[Form] Allow specifying CollectionType's "prototype_name" as callable #57231

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

Open
wants to merge 2 commits into
base: 7.4
Choose a base branch
from

Conversation

adrolter
Copy link
Contributor

@adrolter adrolter commented May 29, 2024

Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues N/A
License MIT

Nesting CollectionTypes which utilize prototypes requires diligently defining prototype_name manually to avoid collisions of the default value (__name__). This has been a pain point since I started using prototypes and I've long wished to be able to dynamically specify the placeholder. I added the callable type to the allowed types for prototype_name and call it once at the beginning of buildForm() to resolve the actual (string) value, and then store that in a FormConfig resolved_prototype_name attribute in case it needs to be accessed later.

In my application code I extend CollectionType (via getExtendedTypes()), set the default prototype_name option value to a function that generates a placeholder name containing random hex characters, and in buildView() inject that placeholder (retrieved via the FormConfig attribute) into an HTML attribute for use by the front-end JS code. This provides a set-and-forget solution where I can happily nest as many prototyped forms as I need without concerning myself with the placeholders.

Example implementation of a CollectionType extension that generates random placeholders:

class CollectionTypeExtension
    extends AbstractTypeExtension
{
    private ?Randomizer $randomizer = null;
    private array $usedPrototypeNames = [];

    public static function getExtendedTypes(): iterable
    {
        return [CollectionType::class];
    }

    public function configureOptions(
        OptionsResolver $resolver,
    ): void
    {
        $resolver->setDefaults([
            'prototype_name_random_bytes' => 3,
            'prototype_name' => function (array $options): string {
                do {
                    $value = \sprintf(
                        '_:%s:_',
                        \bin2hex(
                            ($this->randomizer ??= new Randomizer(new Xoshiro256StarStar()))
                                ->getBytes($options['prototype_name_random_bytes'])
                        ),
                    );
                } while (isset($this->usedPrototypeNames[$value]));

                $this->usedPrototypeNames[$value] = true;

                return $value;
            },
        ]);
    }

    public function buildView(
        FormView $view,
        FormInterface $form,
        array $options,
    ): void
    {
        if (
            $options['prototype']
            && !isset($view->vars['attr']['data-prototype-name'])
            && $form->getConfig()->hasAttribute('resolved_prototype_name')
        ) {
            $view->vars['attr']['data-prototype-name'] = $form->getConfig()->getAttribute('resolved_prototype_name');
        }
    }
}

@adrolter adrolter force-pushed the dynamic_form_prototype_name branch from bbe1581 to bbf036a Compare May 30, 2024 05:03
Co-authored-by: Oskar Stark <oskarstark@googlemail.com>
@fabpot fabpot modified the milestones: 7.2, 7.3 Nov 20, 2024
@fabpot fabpot modified the milestones: 7.3, 7.4 May 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants