{"id":18953264,"url":"https://github.com/symfonycasts/dynamic-forms","last_synced_at":"2025-05-15T23:06:39.862Z","repository":{"id":191793443,"uuid":"685660145","full_name":"SymfonyCasts/dynamic-forms","owner":"SymfonyCasts","description":"Add dynamic/dependent fields to Symfony forms","archived":false,"fork":false,"pushed_at":"2024-11-13T21:51:17.000Z","size":38,"stargazers_count":115,"open_issues_count":17,"forks_count":10,"subscribers_count":9,"default_branch":"main","last_synced_at":"2025-05-11T13:57:37.651Z","etag":null,"topics":["dynamic-fields","symfony","symfony-forms","symfony-ux"],"latest_commit_sha":null,"homepage":"https://symfonycasts.com","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SymfonyCasts.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-31T18:08:19.000Z","updated_at":"2025-05-09T17:49:34.000Z","dependencies_parsed_at":"2023-11-28T19:28:18.164Z","dependency_job_id":"8c2ab65b-4ff7-412e-a4ec-611d057e6aad","html_url":"https://github.com/SymfonyCasts/dynamic-forms","commit_stats":null,"previous_names":["symfonycasts/dynamic-forms"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fdynamic-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fdynamic-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fdynamic-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SymfonyCasts%2Fdynamic-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SymfonyCasts","download_url":"https://codeload.github.com/SymfonyCasts/dynamic-forms/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254436944,"owners_count":22070946,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dynamic-fields","symfony","symfony-forms","symfony-ux"],"created_at":"2024-11-08T13:37:48.325Z","updated_at":"2025-05-15T23:06:34.843Z","avatar_url":"https://github.com/SymfonyCasts.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dynamic / Dependent Symfony Form Fields\n\n[![CI](https://github.com/SymfonyCasts/dynamic-forms/actions/workflows/ci.yaml/badge.svg)](https://github.com/SymfonyCasts/dynamic-forms/actions/workflows/ci.yaml)\n\n**NOTE**: This package is currently experimental. It seems to work great - but\nforms are complex! If you find a bug, please open an issue!\n\nEver have a form field that depends on another?\n\nYou can find a [Demo with LiveComponent on Symfony UX](https://ux.symfony.com/demos/live-component/dependent-form-fields).\n\n* Show a field only if another field is set to a specific value;\n* Change the options of a field based on the value of another field;\n* Have multiple-level dependencies (e.g. field A depends on field B\n  which depends on field C).\n\n```php\npublic function buildForm(FormBuilderInterface $builder, array $options): void\n{\n    $builder = new DynamicFormBuilder($builder);\n\n    $builder-\u003eadd('meal', ChoiceType::class, [\n        'choices' =\u003e [\n            'Breakfast' =\u003e 'breakfast',\n            'Lunch' =\u003e 'lunch',\n            'Dinner' =\u003e 'dinner',\n        ],\n    ]);\n\n    $builder-\u003eaddDependent('mainFood', ['meal'], function(DependentField $field, string $meal) {\n        // dynamically add choices based on the meal!\n        $choices = ['...'];\n\n        $field-\u003eadd(ChoiceType::class, [\n            'placeholder' =\u003e null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal-\u003egetReadable()),\n            'choices' =\u003e $choices,\n            'disabled' =\u003e null === $meal,\n        ]);\n    });\n```\n\n## Installation\n\nInstall the package with:\n\n```bash\ncomposer require symfonycasts/dynamic-forms\n```\n\nDone - you're ready to build dynamic forms!\n\n## Usage\n\nSetting up a dependent field is two parts:\n\n1. [Usage in PHP](#usage-in-php) - set up your Symfony form to handle\n   the dynamic fields;\n2. [Updating the Frontend](#updating-the-frontend) - adding code to your\n   frontend so that when one field changes, part of the form is re-rendered.\n\n## Usage in PHP\n\nStart by wrapping your `FormBuilderInterface` with a `DynamicFormBuilder`:\n\n```php\nuse Symfonycasts\\DynamicForms\\DynamicFormBuilder;\n// ...\n\npublic function buildForm(FormBuilderInterface $builder, array $options): void\n{\n    $builder = new DynamicFormBuilder($builder);\n\n    // ...\n}\n```\n\n`DynamicFormBuilder` has all the same methods as `FormBuilderInterface` plus\none extra: `addDependent()`. If a field depends on another, use this method\ninstead of `add()`\n\n```php\n// src/Form/FeedbackForm.php\n\n// ...\nuse Symfonycasts\\DynamicForms\\DependentField;\nuse Symfonycasts\\DynamicForms\\DynamicFormBuilder;\n\nclass FeedbackForm extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder =  new DynamicFormBuilder($builder);\n\n        $builder-\u003eadd('rating', ChoiceType::class, [\n            'choices' =\u003e [\n                'Select a rating' =\u003e null,\n                'Great' =\u003e 5,\n                'Good' =\u003e 4,\n                'Okay' =\u003e 3,\n                'Bad' =\u003e 2,\n                'Terrible' =\u003e 1\n            ],\n        ]);\n\n        $builder-\u003eaddDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {\n            if (null === $rating || $rating \u003e= 3) {\n                return; // field not needed\n            }\n\n            $field-\u003eadd(TextareaType::class, [\n                'label' =\u003e 'What went wrong?',\n                'attr' =\u003e ['rows' =\u003e 3],\n                'help' =\u003e sprintf('Because you gave a %d rating, we\\'d love to know what went wrong.', $rating),\n            ]);\n        });\n    }\n}\n```\n\nThe `addDependent()` method takes 3 arguments:\n\n1. The name of the field to add;\n2. The name (or names) of the field that this field depends on;\n3. A callback that will be called when the form is submitted. This callback\n   receives a `DependentField` object as the first argument then the\n   value of each dependent field as the next arguments.\n\nBehind the scenes, this works by registering several form event listeners.\nThe callback be executed when the form is first created (using the initial\ndata) and then again when the form is submitted. This means that the callback\nmay be called multiple times.\n\nRendering the field is the same - just be sure to make sure the field exists\nif it's conditionally added:\n\n```twig\n{{ form_start(form) }}\n    {{ form_row(form.rating) }}\n\n    {% if form.badRatingNotes is defined %}\n        {{ form_row(form.badRatingNotes) }}\n    {% endif %}\n\n    \u003cbutton\u003eSend Feedback\u003c/button\u003e\n{{ form_end(form) }}\n```\n\n## Updating the Frontend\n\nIn the previous example, when the `rating` field changes, the form (or part of\nthe form) needs to be re-rendered so the `badRatingNotes` field can be added.\n\nThis library doesn't handle this for you, but here are the 2 main options:\n\n### A) Use [Live Components](https://symfony.com/bundles/ux-live-component/current/index.html)\n\nThis is the easiest method: by rendering your form inside a live component,\nit will automatically re-render when the form changes.\n\n### B) Use [Symfony UX Turbo](https://symfony.com/bundles/ux-turbo/current/index.html#decomposing-complex-pages-with-turbo-frames)\n\nIf you are already using Symfony UX Turbo on your website, you can have a dynamic form running quickly without any JavaScript.\n\nOr you may want to install Symfony UX Turbo, [check out the documentation](https://symfony.com/bundles/ux-turbo/current/index.html#installation).\n\n\u003e [!NOTE]\n\u003e You only need to have Turbo Frame, you can disable Turbo Drive if you do not use it, or do not want to use it.\n\u003e ie: `Turbo.session.drive = false;`\n\nSimply add a `\u003cturbo-frame\u003e` around your form:\n\n```twig\n\u003cturbo-frame id=\"rating-form\"\u003e\n    {{ form(form) }}\n\u003c/turbo-frame\u003e\n```\n\nFrom here you need two small changes:\n\nFirst, in your form type:\n - You need to add an attribute on the choice field, so it auto-submits the form when changed (may need to be adapted to your own form if more complex)\n - Add a submit button, so in the controller you can differenciate from an auto-submit versus a user action\n\n\n```diff\n// src/Form/FeedbackForm.php\n\n// ...\n\nclass FeedbackForm extends AbstractType\n{\n    public function buildForm(FormBuilderInterface $builder, array $options)\n    {\n        $builder =  new DynamicFormBuilder($builder);\n\n        $builder-\u003eadd('rating', ChoiceType::class, [\n            'choices' =\u003e [\n                'Select a rating' =\u003e null,\n                'Great' =\u003e 5,\n                'Good' =\u003e 4,\n                'Okay' =\u003e 3,\n                'Bad' =\u003e 2,\n                'Terrible' =\u003e 1\n            ],\n+           // This will allow the form to auto-submit on value change\n+           'attr' =\u003e ['onchange' =\u003e 'this.form.requestSubmit()'],\n        ]);\n+       // This will allow to differenciate between a user submition and an auto-submit\n+       $builder-\u003eadd('submit', SubmitType::class, [\n+           'attr' =\u003e ['value' =\u003e 'submit'], // Needed for Turbo\n+       ]);\n\n        $builder-\u003eaddDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {\n            if (null === $rating || $rating \u003e= 3) {\n                return; // field not needed\n            }\n\n            $field-\u003eadd(TextareaType::class, [\n                'label' =\u003e 'What went wrong?',\n                'attr' =\u003e ['rows' =\u003e 3],\n                'help' =\u003e sprintf('Because you gave a %d rating, we\\'d love to know what went wrong.', $rating),\n            ]);\n        });\n    }\n}\n```\n\nSecond, in your controller:\n - Specify the action on your form, [this is needed for Turbo Frame](https://symfony.com/bundles/ux-turbo/current/index.html#3-form-response-code-changes)\n - Handle the auto-submit by checking if the button has been clicked\n\n```diff\n// src/Controller/FeedbackController.php\n\n    #[Route('/feedback', name: 'feedback')]\n    public function feedback(Request $request): Response\n    {\n        //...\n\n-       $feedbackForm = $this-\u003ecreateForm(FeedbackForm::class);\n+       $feedbackForm = $this-\u003ecreateForm(FeedbackForm::class, options: [\n+           // This is needed by Turbo Frame, it is not specific to Dependent Symfony Form Fields\n+           'action' =\u003e $this-\u003egenerateUrl('feedback'),\n+       ]);\n        $feedbackForm-\u003ehandleRequest($request);\n        if ($feedbackForm-\u003eisSubmitted() \u0026\u0026 $feedbackForm-\u003eisValid()) {\n\n+           /** @var SubmitButton $submitButton */\n+           $submitButton = $feedbackForm-\u003eget('submit');\n+           if (!$submitButton-\u003eisClicked()) {\n+               return $this-\u003erender('feedback.html.twig', ['feedbackForm' =\u003e $feedbackForm]);\n+           }\n\n            // Your code here\n            // ...\n\n            return $this-\u003eredirectToRoute('home');\n        }\n\n        return $this-\u003erender('feedback.html.twig', ['feedbackForm' =\u003e $feedbackForm]);\n    }\n\n```\n\n### C) Write custom JavaScript\n\nIf you're not using Live Components, nor Turbo Frames, you'll need to write some custom\nJavaScript to listen to the `change` event on the `rating` field and then\nmake an AJAX call to re-render the form. The AJAX call should submit the\nform to its usual endpoint (or any endpoint that will submit the form), take\nthe HTML response, extract the parts that need to be re-rendered and then replace\nthe HTML on the page.\n\nThis is a non-trivial task and there may be room for improvement in this\nlibrary to make this easier. If you have ideas, please open an issue!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfonycasts%2Fdynamic-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsymfonycasts%2Fdynamic-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsymfonycasts%2Fdynamic-forms/lists"}