idci/extra-form-bundle
IDCI ExtraFormBundle extends Symfony’s Form component with additional form types and helpers, making it easier to build richer forms with less custom code. It integrates cleanly into Symfony applications and reduces boilerplate for common advanced form fields.
This is a Symfony2-era bundle (small install base, MIT, no dependents) whose purpose is to let a FormType describe its children at runtime instead of at compile time. Treat it as a niche tool: read the source before adopting, and budget time for Symfony-version friction.
Minimal steps:
app/AppKernel.php:
new IDCI\Bundle\ExtraFormBundle\IDCIExtraFormBundle(),
There is almost certainly no meaningful container configuration — check DependencyInjection/ for the extension class and whether any services.yml parameters are exposed.Form/Type/ (the type that actually builds dynamic children), Form/Extension/ or Form/EventListener/ (the form-event subscribers that re-add fields on submit), and Resources/doc/. For a bundle this small, the code is the documentation.The canonical starting point is a single form type that accepts a field-definition collection via an option, something in the shape of:
$form = $this->createForm(ExtraFormType::class, $data, [
'fields' => $fieldDefinitions, // name => ['type' => ..., 'options' => [...]]
]);
Verify the exact option name and definition format against the bundle's Form/Type directory before writing anything real — that contract is the whole API surface.
Definition-driven forms. Store field definitions as an array or a Doctrine entity (name, type, options, position). Pass them into the form type; the bundle iterates and calls $builder->add($name, $type, $options). Keep the definition objects serializable and immutable — they become your schema, so version them if users can edit them.
Per-entity dynamic forms. The common real-world case: a Product with a JSON attributes column and an AttributeDefinition table. Use a data transformer to map the flat form array to/from the JSON column so the rest of your code sees a plain array. Do not let the dynamic form type guess the data class; set data_class => null and handle mapping yourself.
Rebuilding on submit. Dynamic forms break the moment the submitted payload differs from the built form (conditional fields, AJAX-added rows). The pattern is a PRE_SUBMIT listener that reads the raw submitted data, recomputes the definition list, and adds the corresponding fields. If the bundle already ships this listener, extend it rather than wrapping it; if it does not, this is the first thing you write.
Conditional / dependent fields. Combine with core FormEvents::POST_SET_DATA + PRE_SUBMIT to add or remove children based on sibling values, and mark removed fields with 'mapped' => false rather than deleting them blindly so validation errors do not leak from stale submissions.
Validation. Dynamic children get no annotations, so every constraint must live in the field definition's options['constraints']. Build the definitions with constraints attached, and validate the definition itself (allowed types, allowed constraint classes) at the point of storage — this is your only real guardrail.
Admin UI integration. Keep form-definition editing in a separate, conventional CollectionType form. Do not let the dynamic form edit its own schema; that recursion is where these systems become unmaintainable.
Rendering. Expect to render fields in a loop ({% for child in form %}{{ form_row(child) }}{% endfor %}) and to need form_rest ordering by your position key. Custom themes are usually necessary because Symfony's default ordering is insertion order.
getBlockPrefix() vs getName(), Types::TEXT vs string type names, and choices_as_values deprecations. Pin your Symfony version or budget a port. On Symfony 4.4+ you can often replace this bundle entirely with FormEvents plus CollectionType — evaluate that before committing.This form should not contain extra fields when the submitted keys do not match the built children. Any dynamic bundle must re-add children in PRE_SUBMIT; if you see that exception, your listener order or definition recomputation is wrong.getData() returns an array, not your entity. With data_class => null you lose automatic mapping. Use a data transformer or map manually in the controller; do not assume $form->getData() gives you a populated object.mapped => false. Dynamic children that do not correspond to a property must be explicitly unmapped, otherwise PropertyAccess throws Neither the property ... nor one of the methods ... exist.type argument.buildForm for every nested form is not. Cache the definition list (Doctrine result cache or a request-scoped service) and pass it in as an option.$builder->get('field') inside PRE_SUBMIT will fail if the field was not added in PRE_SET_DATA. Dump $form->all() at the top of each listener, and enable the form profiler panel to see what actually got built versus submitted.getExtendedType()) plus your own event subscriber registered on the form — cheaper than forking, and survives minor version bumps. Only subclass the bundle's type if the option contract is genuinely too narrow.src/ if you adopt it, keep the license header, and isolate it behind one of your own form types so you can swap the implementation later.PRE_SUBMIT rebuild or add Symfony 4/5 compatibility, upstream it — for a bundle this size, one contributor makes the difference between usable and stranded.How can I help you explore Laravel packages today?