Product Decisions This Supports
- Single-source-of-truth decision for static vocabularies. The core product question this package answers is: "where does the canonical list of genders, civilities, salutations, or statuses live?" Today most teams duplicate those lists across form definitions, validation rules, seeders/fixtures, Blade/Twig templates, and API transformers. This bundle is evidence that the "declare once in config, consume everywhere" pattern is cheap to implement (~88 stars, MIT, small surface area) and worth adopting as an internal convention even if the package itself isn't installed.
- Build vs. buy for enum-like domain data. For modern PHP (8.1+) the honest comparison is this package vs. native backed enums. Enums win on type safety, IDE support, and zero dependency; this package wins when non-developers need to change labels, when labels must be localized, or when the same dictionary must feed forms, validation, Twig, and Faker from one place.
- Localization and display-label roadmap. Dictionaries carry display labels, not just values, which directly supports i18n decisions (e.g., "Mme/M." vs. "Mrs./Mr.") without touching code paths. This is the strongest product argument for a config registry over hardcoded constants.
- Fixture/seed and demo-data strategy. The Faker adapter means seeded environments and demos draw from the same vocabulary as production forms — a small but real reduction in "fake data doesn't match real choices" bugs.
- Use cases where it fits: registration/profile forms (civility, gender), address/salutation fields, fixed status or category pickers, document types, and any "static choice repeated in three or more places."
- New in v3.6.0 — named dictionary autowiring. Multiple distinct dictionaries can now be injected by name rather than fetched from a collection. Product relevance: this is the feature that makes the bundle viable when a product has several independent vocabularies (e.g.,
civility, country, document_type) each owned by a different form/domain, instead of a single global registry. It converts "one big list bucket" into "a typed, per-domain vocabulary layer" — a meaningfully better fit for larger catalogs.
- New in v3.6.0 — documented custom dictionary implementations. The package now documents how to implement the dictionary contract yourself. That partially rewrites the "static-only" boundary: a database-backed or per-tenant source can be adapted to satisfy the same interface and reuse the form/validator/Twig adapters. It does not give you an admin UI, hierarchy support, or cache invalidation — those remain a build decision — but the integration cost for dynamic data is now documented rather than inferred, which lowers the estimate on "we'd have to fork it."
- Not supported by this package: admin-editable taxonomy management, hierarchy/tree-shaped data, per-tenant dictionary storage, or cache/refresh semantics for dynamic sources. Those remain a build decision even with the new custom-dictionary docs.
When to Consider This Package
Adopt when all of these hold:
- The application is Symfony on a version covered by the CI matrix (now including Symfony 7.1) — the value is entirely in the framework adapters (DI compiler passes, Form types, Validator constraints, Twig, Web Profiler). There is no Laravel service provider, no
config/dictionary.php, and no Blade/validation-rule integration.
- You have many static dictionaries reused across forms, validation, templates, and fixtures — the payoff scales with repetition, not with a single gender field. v3.6.0's named autowiring improves this case specifically: many dictionaries are now cheaper to inject than to look up.
- You want labels and values to be editable without a code deploy, or you need localized labels in one place.
- Lazy (callable/iterator-backed) dictionaries matter to you — the documented contract is lazy until first value access, which is genuinely useful for expensive or large vocabularies.
- You need a dynamic or tenant-specific source but want to keep the adapter layer — the newly documented custom-implementation path lets you satisfy the contract from your own data source and still get form/validator/Twig/Faker integration. Verify the adapter behavior against your source before committing; the docs describe the contract, not the caching.
Look elsewhere when:
- You are on Laravel. This remains the decisive filter and v3.6.0 does not change it. The bundle is Symfony-coupled end to end; porting it means reimplementing the DI, validation, and templating layers. In Laravel,
config/*.php arrays plus Rule::in(...) plus PHP enums cover the same need with zero dependency.
- PHP 8.1+ native enums already satisfy the requirement. If labels are static and translations can live in lang files, a backed enum is simpler and better-typed.
- You need user-managed, hierarchical, or cache-sensitive data with a UI. Custom implementations are now documented, but the bundle still gives you no storage, administration, or invalidation story.
- Adoption risk is unacceptable. Still 0 dependents and ~88 stars: no ecosystem validation, no third-party tutorials. This release is mildly reassuring — a new contributor (@ErwannRousseau) authored four of the substantive PRs and the bundle now advertises PHP 8.4/8.5 and Symfony 7.1 — but the bus factor argument stands. Treat it as "vendor this pattern," not "depend on this package," until cadence is proven over consecutive releases.
- Your team can't absorb the operational quirks. Compiler-pass ordering is significant, and documented DI tags must be verified against the passes that actually consume them. Named autowiring removes some of this boilerplate but does not remove the ordering constraint — price the maintenance surface in.
How to Pitch It (Stakeholders)
To executives (30 seconds): Small, MIT-licensed Symfony component that removes duplication of fixed choice lists — gender, civility, salutations, statuses — by letting teams declare them once in configuration and reuse them in forms, validation, templates, and test data. The new 3.6.0 release adds named per-dictionary injection, documented support for custom (including database-backed) sources, and PHP 8.4/8.5 plus Symfony 7.1 compatibility — no breaking changes, so it's a safe, low-risk signal about the project's direction, not a migration event. Its real value to us is as a reference pattern: it tells us whether to standardize a single config-driven registry for our fixed vocabularies, and now also gives us a documented escape hatch when a "static" list turns out to need to be dynamic. Caveat: still essentially unused in the wild (0 dependents, ~88 stars), so we would not take a production dependency without a spike; and it offers nothing for Laravel, where config arrays plus native enums already do this.
To engineering: Declare dictionaries in knp_dictionary.dictionaries, consume via the public Dictionary\Collection and Dictionary\Factory services, or — as of 3.6.0 — inject a specific dictionary by name via autowiring, which is now the preferred path when a consumer needs exactly one vocabulary. Adapters exist for forms, validation, Twig, Faker, and the profiler. Behavior is spec'd with PHPSpec; services are private except the two entry points; dictionaries are lazy until first access — don't eagerly evaluate callable or iterator-backed ones. 3.6.0 also documents implementing the dictionary contract yourself, which is the supported route for database-backed or per-tenant vocabularies — read it before considering a fork, and confirm adapter behavior (especially caching and laziness) against your source. Remaining traps: verify that any DI tag you rely on is actually consumed by a compiler pass, and don't reorder compiler passes or tagged factories. The upgrade itself is dev-dependency churn plus additive features — review the new AGENTS guidance file and the Symfony PHPStan compatibility fix before bumping. If we're not on Symfony, replicate the pattern natively: one config file as source of truth, one helper for choices, one validation rule, one accessor for templates and seeders. Recommend a half-day spike against our own form/validation stack before any adoption decision.