tacman/dictionary-bundle
A Symfony bundle that brings dictionary and glossary functionality to PHP projects. It helps organize terms, definitions, and reference content, making it easier to add structured dictionary features to Symfony applications.
Install and register. composer require tacman/dictionary-bundle. On Symfony Flex the bundle is auto-registered; otherwise add it to config/bundles.php for the dev and test environments too (a common miss — the bundle is invisible in dev if you only register it under all).
Read README.md first, then src/DependencyInjection/. This bundle is small enough that the compiler pass and the extension class tell you more about how dictionaries are discovered than any docblock will. Look for the service tag name (historically something like tacman.dictionary — confirm the exact string in the extension’s registerForAutoconfiguration/addTaggedService logic before copying examples).
Inspect the registry service. The core object you will inject everywhere is the dictionary registry (DictionaryRegistry or similarly named). Run bin/console debug:container --tag=<tag-name> after install to confirm it is populated; an empty debug:container result is the fastest way to diagnose "my dictionary isn't showing up."
First use case: replace a hard-coded gender/civility list. Create a class implementing the bundle’s DictionaryInterface (or extending its abstract base) with getName(): string and getChoices(): array. Register it as a service (autoconfigure should apply the tag), clear cache, then consume it:
// Form type
$builder->add('gender', ChoiceType::class, [
'choices' => $this->dictionaries->get('gender')->getChoices(),
]);
Sanity check in Twig once the form path works — the bundle typically ships a Twig extension so you can render labels from keys without reaching into the registry from the template.
Dictionary-as-service (preferred over constants). Define one class per domain concept (GenderDictionary, CivilityDictionary, CountryDictionary) rather than a single god-class. Each one is a normal Symfony service, so you get constructor injection of the translator, repository, or config for free. Keys are the stable contract; labels are the translation keys.
Form Type reuse. The highest-leverage pattern: build a DictionaryChoiceType (or use the bundle’s if it provides one) that takes a dictionary name in configureOptions and resolves it via the registry. Then every form field becomes ->add('gender', DictionaryChoiceType::class, ['dictionary' => 'gender']) and adding a new civility option is a one-line edit in the dictionary, not a grep across ten form types.
API/frontend handoff. Expose a controller endpoint that iterates the registry and returns [{name: 'gender', choices: {…}}, …] so a JS/Vue/React frontend shares the same source of truth as the server-rendered forms. Cache the JSON response (HTTP cache or a small in-memory cache) — dictionaries are static and should not query the DB per request.
Localization layer. If your dictionary returns translation keys as labels, resolve them with |trans in Twig or $translator->trans() in PHP, and put the keys in a single dictionaries.en.yaml domain. Keep keys namespaced (dictionary.gender.male) so they don’t collide with user-facing UI strings.
Extension point for dynamic dictionaries. If a dictionary must come from the DB (e.g. departments), implement the interface on a repository-backed service rather than editing static arrays. The registry doesn’t care where choices come from; just make sure the service is lazy if the DB call is expensive.
Testing. In a functional test, self::getContainer()->get(DictionaryRegistry::class) and assert the expected keys exist. That catches a broken tag registration before it shows up as an empty select box in staging.
dev/test. Verify the bundle is listed for all environments you actually run; a missing entry in config/bundles.php produces a confusing "service not found" rather than a bundle error.bin/console cache:clear in dev before debug:container --tag=… reveals it. If autoconfiguration is on, also confirm your class is in a scanned namespace under services.yaml.ChoiceType and most dictionary implementations use string keys; if your dictionary stores integer codes, be explicit about choice_value/choice_label callbacks or you’ll render labels as keys.services.yaml !remove + replacement rather than editing vendor code, so composer update doesn’t erase your civility list.composer.json and read the changelog between tags — there is no ecosystem of downstream users to catch a breaking change for you.dictionary. to catch missing translations.How can I help you explore Laravel packages today?