PHPackages                             kachnitel/dynamic-form-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Database &amp; ORM](/categories/database)
4. /
5. kachnitel/dynamic-form-bundle

ActiveSymfony-bundle[Database &amp; ORM](/categories/database)

kachnitel/dynamic-form-bundle
=============================

Zero-configuration Symfony form generation from Doctrine entity metadata

0.0.3(1mo ago)065↓33.3%MPL-2.0PHPPHP &gt;=8.2

Since Jul 14Pushed 1mo agoCompare

[ Source](https://github.com/kachnitel/dynamic-form-bundle)[ Packagist](https://packagist.org/packages/kachnitel/dynamic-form-bundle)[ RSS](/packages/kachnitel-dynamic-form-bundle/feed)WikiDiscussions master Synced 1w ago

READMEChangelogDependencies (40)Versions (4)Used By (0)

Kachnitel Dynamic Form Bundle
=============================

[](#kachnitel-dynamic-form-bundle)

[![Tests](https://camo.githubusercontent.com/791b99df6c36e5bded80a8f2f4f255146dff56a2f79c423b6f0f58f91f2161b0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3238332532307061737365642d726564)](https://camo.githubusercontent.com/791b99df6c36e5bded80a8f2f4f255146dff56a2f79c423b6f0f58f91f2161b0/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f74657374732d3238332532307061737365642d726564)[![Coverage](https://camo.githubusercontent.com/bdd2521e8d9fefb431882c8ebde606b357b6fe508b813e46c7a2e05d10e9d3c2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d38392532352d627269676874677265656e)](https://camo.githubusercontent.com/bdd2521e8d9fefb431882c8ebde606b357b6fe508b813e46c7a2e05d10e9d3c2/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f636f7665726167652d38392532352d627269676874677265656e)[![Assertions](https://camo.githubusercontent.com/b10b03ad85902bf0285bbe55daeef93eccd8768f6a84d4ff6bd60260af420a42/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f617373657274696f6e732d3731332d626c7565)](https://camo.githubusercontent.com/b10b03ad85902bf0285bbe55daeef93eccd8768f6a84d4ff6bd60260af420a42/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f617373657274696f6e732d3731332d626c7565)[![PHPStan](https://camo.githubusercontent.com/b7912496e993c64ccde54f83292a8614aec552181a87d91eb60b060e0cc000e7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d31302d627269676874677265656e)](https://camo.githubusercontent.com/b7912496e993c64ccde54f83292a8614aec552181a87d91eb60b060e0cc000e7/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d31302d627269676874677265656e)[![PHP](https://camo.githubusercontent.com/1bb3592bd684ca1c1b4f1fb2afa0a51c6910cf6068c262ca26299c7264ca775f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d2533453d382e322d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/1bb3592bd684ca1c1b4f1fb2afa0a51c6910cf6068c262ca26299c7264ca775f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d2533453d382e322d3737374242343f6c6f676f3d706870266c6f676f436f6c6f723d7768697465)[![Symfony](https://camo.githubusercontent.com/bca237fded95b94d7947a4231da294710525ceea77da959f84987fa388eb7294/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53796d666f6e792d253545362e34253743253545372e30253743253545382e302d3030303030303f6c6f676f3d73796d666f6e79266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/bca237fded95b94d7947a4231da294710525ceea77da959f84987fa388eb7294/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f53796d666f6e792d253545362e34253743253545372e30253743253545382e302d3030303030303f6c6f676f3d73796d666f6e79266c6f676f436f6c6f723d7768697465)

[![Doctrine ORM](https://camo.githubusercontent.com/b38abeabbbe7a168c19d0a6ad76ec3075318a17ac33d8bd52c14e895bc34db20/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f637472696e652532304f524d2d253545332e352d4643364133313f6c6f676f3d646f637472696e65266c6f676f436f6c6f723d7768697465)](https://camo.githubusercontent.com/b38abeabbbe7a168c19d0a6ad76ec3075318a17ac33d8bd52c14e895bc34db20/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f446f637472696e652532304f524d2d253545332e352d4643364133313f6c6f676f3d646f637472696e65266c6f676f436f6c6f723d7768697465)[![PHPStan](https://camo.githubusercontent.com/022b70e6631d055205dfebf2aa7e53b3f63e7a3ea04a18e86429f279e29a29f1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c25323031302d627269676874677265656e)](https://camo.githubusercontent.com/022b70e6631d055205dfebf2aa7e53b3f63e7a3ea04a18e86429f279e29a29f1/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c25323031302d627269676874677265656e)[![License](https://camo.githubusercontent.com/7ad3f31060a6c248e05873d9b4f20c463f3408798d94335154d382867caf3cc3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d504c2d2d322e302d626c7565)](https://camo.githubusercontent.com/7ad3f31060a6c248e05873d9b4f20c463f3408798d94335154d382867caf3cc3/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d504c2d2d322e302d626c7565)

Zero-configuration Symfony form generation from Doctrine entity metadata. Point `DynamicEntityFormType` at an entity and get a working create/edit form — scalar fields, associations, and [Symfony UX](https://symfony.com/bundles/ux-live-component/current/index.html) LiveComponent collections included — without writing a `FormType` class.

Extracted from [kachnitel/admin-bundle](https://github.com/kachnitel/FrdAdminBundle), where it serves as the auto-form engine behind the generic CRUD controller. Usable standalone in any Symfony + Doctrine application. **License: MPL-2.0** (file-level copyleft, compatible with proprietary use — see [License](#license)).

Quick Start
-----------

[](#quick-start)

### 1. Install

[](#1-install)

```
composer require kachnitel/dynamic-form-bundle
```

Register in `config/bundles.php`:

```
Kachnitel\DynamicFormBundle\KachnitelDynamicFormBundle::class => ['all' => true],
```

No further configuration — the bundle has no config tree.

### 2. Build a form

[](#2-build-a-form)

```
use Kachnitel\DynamicFormBundle\Form\DynamicEntityFormType;

$form = $this->createForm(DynamicEntityFormType::class, $product, [
    'entity_class' => Product::class,
]);
```

`entity_class` is the only required option. `data_class` defaults to `entity_class`, so binding the form straight to `Product` needs nothing further — pass `data_class` explicitly only when it should differ (a DTO, or `null` for an unmapped form). See [Form Options](#form-options) for all options.

### 3. That's it

[](#3-thats-it)

Every non-identifier scalar field and owning-side association on `Product` gets a form field, with type-appropriate widgets, validation, and nullability handling derived from Doctrine metadata. Doctrine `string` fields with a matching validator constraint (`#[Assert\Email]`, `#[Assert\Url]`, …) are upgraded to the corresponding widget automatically — see [Type Guessing](docs/TYPE_GUESSING.md).

Form Options
------------

[](#form-options)

OptionTypeDefaultDescription`entity_class``string`**required**Fully-qualified entity class name to introspect`data_class``string|null``entity_class`The class submitted values are mapped onto. Lazily defaults to `entity_class` — only evaluated when omitted entirely. Pass explicitly to bind elsewhere, including `null` for an unmapped form bound to a plain array/DTO`is_root``bool``true`Set `false` for child forms inside `LiveCollectionType` to prevent collection associations from being re-added (avoids infinite recursion in bidirectional relationships)`entity_instance``object|null``null`The entity being edited. Forwarded to `FieldEditabilityResolverInterface` for per-row editability checks; pass a fresh `new Entity()` for create forms if your resolver needs an instanceSupported Field Types
---------------------

[](#supported-field-types)

Doctrine typeForm typeWidget options`string``TextType`Upgraded by constraint or naming convention — see [Type Guessing](docs/TYPE_GUESSING.md)`text``TextareaType``integer`, `smallint`, `bigint``IntegerType``decimal`, `float``NumberType``html5: true``boolean``CheckboxType`Always `required: false``date`, `date_immutable``DateType``widget: single_text``datetime`, `datetimetz`, `datetime_immutable`, `datetimetz_immutable``DateTimeType``widget: single_text``time`, `time_immutable``TimeType``widget: single_text`Backed PHP enum (via `enumType`)`EnumType``json`, `array`, `simple_array`, `object`, `blob`, `binary` are silently skipped — no field, no error.

Nullability, `empty_data`, and required-field validation have non-obvious behaviour driven by Symfony transformer quirks. See [Field Mapping](docs/FIELD_MAPPING.md) for the full story.

Associations
------------

[](#associations)

Doctrine typeForm typeUI`ManyToOne`, `OneToOne` (owning)`EntityType`Autocomplete dropdown`ManyToMany` (owning side)`EntityType` with `multiple: true`Autocomplete multi-select`OneToMany``LiveCollectionType` with recursive `DynamicEntityFormType`Add / remove rowsTwo categories of association are **automatically skipped** — re-include them by having `FieldEditabilityResolverInterface::isExplicitOverride()` return `true` for that property:

- Inverse-side associations (`mappedBy` set) — except `OneToMany` in root forms, which is always kept
- Single-valued associations with `inversedBy` set (a ManyToOne/OneToOne owning side pointing back at a parent's collection)

> **Default behaviour with `AlwaysEditableFieldResolver`:** both `canEdit()` and `isExplicitOverride()` return `true` unconditionally, so all associations — including the normally-skipped categories above — are included by default. The auto-skip rules only take effect once you bind a resolver that returns `false` for `isExplicitOverride()` on associations it does not explicitly opt back in.

See [Associations](docs/ASSOCIATIONS.md) for the full auto-skip table, `OneToMany` `cascade`/`orphanRemoval`/adder-remover requirements, and troubleshooting.

Controlling Field Inclusion
---------------------------

[](#controlling-field-inclusion)

```
interface FieldEditabilityResolverInterface
{
    // General include/exclude gate. $entity is null when building a "new entity" form
    // or before LiveCollectionType binds a row — treat null as "include provisionally"
    // (DynamicFormEditabilityListener re-checks on PRE_SET_DATA once a real instance exists).
    public function canEdit(string $entityClass, string $property, ?object $entity = null): bool;

    // Opt back in to a structurally auto-skipped association (inverse side or parent back-reference).
    // Must return true ONLY for an explicit per-property override, never by entity-level default —
    // a permissive entity-wide default must not silently pull every back-reference into the form.
    public function isExplicitOverride(string $entityClass, string $property, ?object $entity = null): bool;
}
```

Default binding: `AlwaysEditableFieldResolver` — both methods return `true`. Override in `services.yaml`:

```
Kachnitel\DynamicFormBundle\Editability\FieldEditabilityResolverInterface:
    alias: App\Form\MyFieldEditabilityResolver
```

See [Editability](docs/EDITABILITY.md) for the full contract, the two-method design rationale, `DynamicFormEditabilityListener` (the `PRE_SET_DATA` re-check), and a `kachnitel/admin-bundle` compiler-pass example.

Controlling Field Widgets
-------------------------

[](#controlling-field-widgets)

Doctrine `string` fields are upgraded from `TextType` to a more specific widget when a matching validator constraint is present — `#[Assert\Email]` → `EmailType`, `#[Assert\Url]` → `UrlType`, `#[Assert\Country]` → `CountryType`, etc. Powered by Symfony's own `form.type_guesser.validator`, wired automatically.

An optional naming-convention guesser (`ConventionalFieldTypeGuesser`, ships in this bundle) covers `tel`/`color`/`search`/`email`/`url` by field name for fields with no matching constraint. Not enabled by default — see [Type Guessing](docs/TYPE_GUESSING.md) for the opt-in recipe and how to write your own guesser.

How It WorksFour collaborating pieces:

ClassResponsibility`DoctrineFormTypeMapper`Maps a single Doctrine field/association mapping to a Symfony form field config (type + options)`DynamicEntityFormType`Walks entity metadata, calls the mapper for each field/association, decides what to include`FieldEditabilityResolverInterface`Extension point — decides whether a property should be in the form at all`FormTypeGuesserInterface` (Symfony's own)Extension point — upgrades a `string` field to a more specific type than `TextType``DynamicEntityFormType` has no knowledge of attributes, expressions, or permissions — every inclusion decision beyond Doctrine's structural rules (the identifier field, unsupported types) is delegated to the injected `FieldEditabilityResolverInterface`. The bundle ships a permissive default; consumers override the service alias.

`DynamicFormEditabilityListener` is a `FormEvents::PRE_SET_DATA` listener manually registered inside `DynamicEntityFormType::buildForm()` (not a DI-managed service — it needs the specific entity class and resolver instance from that build call). It re-runs `canEdit()` for every field once a real entity instance is bound, covering `LiveCollectionType` child forms where `buildForm()` runs before any row data is available. It can only **remove** fields already added by `buildForm()`; it never re-adds an association that was skipped at build time.

Documentation
-------------

[](#documentation)

GuideCovers[Field Mapping](docs/FIELD_MAPPING.md)Full type table, nullability cross-check, `empty_data` transformer quirks, `RequiredValueTransformer`, duplicate-validation prevention[Associations](docs/ASSOCIATIONS.md)Collection mapping, `cascade`/`orphanRemoval`/adder-remover requirements, auto-skip rules, infinite-recursion prevention, troubleshooting[Editability](docs/EDITABILITY.md)`FieldEditabilityResolverInterface` contract, two-method design rationale, `DynamicFormEditabilityListener`, `kachnitel/admin-bundle` real-world example[Type Guessing](docs/TYPE_GUESSING.md)Constraint-driven and naming-convention widget upgrades, `FormTypeGuesserInterface` extension point, `kachnitel/admin-bundle` opt-in recipeDevelopment```
composer test       # phpstan (level 10) + phpcs + phpunit + phpmd
composer phpstan
composer phpunit
composer phpmd
```

Run a specific group:

```
vendor/bin/phpunit --group auto-form        # DynamicEntityFormType + DoctrineFormTypeMapper core
vendor/bin/phpunit --group collections      # Association collection handling
vendor/bin/phpunit --group dynamic-form     # DynamicEntityFormType + DynamicFormEditabilityListener
vendor/bin/phpunit --group editability      # AlwaysEditableFieldResolver
vendor/bin/phpunit --group form-transformers
vendor/bin/phpunit --group form-exceptions  # NullabilityMismatchException
vendor/bin/phpunit --group inline-add       # data-admin-entity-class attr on EntityType
vendor/bin/phpunit --group type-guessing    # ConventionalFieldTypeGuesser + mapper integration
vendor/bin/phpunit --group integration      # Real ValidatorTypeGuesser smoke tests
```

Requirements- PHP 8.2+
- Symfony 6.4 / 7.0 / 8.0 — `doctrine-bridge`, `form`, `framework-bundle`, `validator`
- Doctrine ORM 3.5+, `doctrine/doctrine-bundle` ^3.0
- Symfony UX Live Component ^2.13 (for `OneToMany` → `LiveCollectionType`)
- Symfony UX Autocomplete ^3.0 (for association `EntityType` autocomplete)
- `symfony/intl` *(suggested)* — required at runtime if any entity uses `#[Assert\Country]`, `#[Assert\Language]`, `#[Assert\Currency]`, or `#[Assert\Locale]` — see [Type Guessing](docs/TYPE_GUESSING.md)

License
-------

[](#license)

**MPL-2.0** — see [LICENSE](LICENSE).

File-level copyleft: distributing a *modified* version of a file from this package requires that file's source to remain available under MPL-2.0. Using the package unmodified in a proprietary or closed-source application is fine. No network-use clause (unlike AGPL) — running it as part of a SaaS service triggers nothing extra. Compatible with `kachnitel/admin-bundle`'s MIT license.

###  Health Score

39

—

LowBetter than 84% of packages

Maintenance93

Actively maintained with recent releases

Popularity11

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity38

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

3

Last Release

35d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/4067705?v=4)[Ondrej Vana](/maintainers/kachnitel)[@kachnitel](https://github.com/kachnitel)

---

Top Contributors

[![kachnitel](https://avatars.githubusercontent.com/u/4067705?v=4)](https://github.com/kachnitel "kachnitel (15 commits)")

---

Tags

symfonysymfony-uxdoctrineformlive component

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan, Rector

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/kachnitel-dynamic-form-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/kachnitel-dynamic-form-bundle/health.svg)](https://phpackages.com/packages/kachnitel-dynamic-form-bundle)
```

###  Alternatives

[easycorp/easyadmin-bundle

Admin generator for Symfony applications

4.3k18.3M433](/packages/easycorp-easyadmin-bundle)[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.5k6.0M780](/packages/sylius-sylius)[rcsofttech/audit-trail-bundle

Enterprise-grade, high-performance Symfony audit trail bundle. Automatically track Doctrine entity changes with split-phase architecture, multiple transports (HTTP, Queue, Doctrine), and sensitive data masking.

12017.1k](/packages/rcsofttech-audit-trail-bundle)[kimai/kimai

Kimai - Time Tracking

4.8k9.4k1](/packages/kimai-kimai)[contao/core-bundle

Contao Open Source CMS

1301.7M3.1k](/packages/contao-core-bundle)[2lenet/crudit-bundle

The easy like Crud'it Bundle.

1617.3k16](/packages/2lenet-crudit-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
