Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Dictionary Bundle Laravel Package

knplabs/dictionary-bundle

KnpDictionaryBundle lets you group tagged services into named dictionaries and fetch them by key at runtime, instead of wiring each dependency individually. Great for plugin-style architectures, handler registries, and any collection of services you want to look up dynamically without boilerplate co...

View on GitHub
Deep Wiki
Context7

Getting Started

What it solves. knplabs/dictionary-bundle lets you declare named, reusable lists (genders, civilities, countries, statuses, units) once in bundle config and then consume them consistently from forms, validators, Twig, fixtures and the profiler — instead of copy-pasting ['male', 'female'] through your codebase and watching the copies drift apart.

Minimal install

composer require knplabs/dictionary-bundle

Register the bundle in your bundle registration file (check KnpDictionaryBundle.php for the exact FQCN — the package uses a legacy nested src/Knp/DictionaryBundle/ layout):

return [
    // ...
    Knp\DictionaryBundle\KnpDictionaryBundle::class => ['all' => true],
];

As of v3.6.0 the package is tested against Symfony 7.1 and PHP 8.4/8.5, so you can adopt it on a current stack without pinning older constraints.

Declare your first dictionary in your dictionary config file:

knp_dictionary:
    dictionaries:
        gender:
            - male
            - female
        civility:
            - mr
            - mrs
            - miss

That is the entire "first use case": a static list you are currently duplicating. Migrate one real list first (gender/civility is the canonical example from the package description) rather than designing every dictionary up front.

Where to look first, in order

Order File Why
1 DependencyInjection/Configuration.php The only authoritative schema for knp_dictionary.dictionaries (plain arrays vs. callable/iterator-backed entries). Docs are thin; read the tree builder.
2 Resources/config/services.yaml Tells you the real service IDs and, as of v3.6.0, the generated named-dictionary wiring.
3 KnpDictionaryBundle.php Shows the compiler passes and their order, i.e. what actually gets wired — including the new named-dictionary pass.
4 Dictionary/ Collection, Factory, Dictionary implementations — the shared runtime everything else consumes.

Confirm the surface area before writing code. Most adapter services are private; the Dictionary\Collection and the Dictionary\Factory alias are the public seams. v3.6.0 adds named dictionary autowiring (see Implementation Patterns below), but the exact service ID format matters — verify with:

bin/console debug:container Knp\\DictionaryBundle\\Dictionary\\Collection
bin/console debug:container --tag=knp_dictionary.factory
bin/console debug:container --show-private Knp\\DictionaryBundle\\Dictionary\\Dictionary\\gender

Implementation Patterns

Named dictionary autowiring (new in v3.6.0)

Previously the only stable injection point was the shared Dictionary\Collection. v3.6.0 adds named dictionary autowiring: the bundle registers a service per dictionary that you can type-hint directly. This is the cleanest option for classes that only care about one list:

use Knp\DictionaryBundle\Dictionary\Dictionary;

final class GenderController
{
    public function __construct(
        private readonly Dictionary $gender,  // matches the dictionary name
    ) {}
}

Verify the alias naming in debug:container before relying on it in production code, and standardise on one style per codebase — mixing named injection and Collection lookups across dozens of classes is a maintenance hazard.

Inject the collection when you need more than one

For handlers, services and validators that touch multiple dictionaries, keep injecting Dictionary\Collection — it remains the stable, public entry point and avoids a growing constructor signature as dictionaries multiply:

use Knp\DictionaryBundle\Dictionary\Collection;

final class RegistrationHandler
{
    public function __construct(private readonly Collection $dictionaries) {}

    public function handle(string $gender): void
    {
        if (!$this->dictionaries->get('gender')->contains($gender)) {
            throw new \InvalidArgumentException('Unknown gender.');
        }
    }
}

Rule of thumb: named autowiring for one-dictionary classes, Collection for anything that spans two or more.

Forms: one form type instead of a new ChoiceType per case

The bundle ships a form adapter. Instead of writing this in every form:

$builder->add('gender', ChoiceType::class, ['choices' => ['Male' => 'male', 'Female' => 'female']]);

use the dictionary-backed type (verify the exact FQCN in src/Knp/DictionaryBundle/Form/) so the choices follow the config automatically:

$builder->add('gender', DictionaryType::class, ['dictionary' => 'gender']);

If the shipped type lacks a feature you need (optgroup support, custom choice labels), wrap it in your own type and pass the dictionary through — do not fork the bundle or subclass its final classes.

Validation: pair the form with the dictionary constraint

A ChoiceType alone is not validation. If the bundle exposes a constraint (see Validator/Constraints/), attach it so API payloads and non-form input are guarded by the same list:

use Knp\DictionaryBundle\Validator\Constraints as DictionaryAssert;

#[DictionaryAssert\Dictionary('gender')]
private string $gender;

If no constraint exists for your use case, build the equivalent with a plain Choice constraint fed from Collection::get('gender')->all(). Avoid a raw Choice with an inlined literal array — that reintroduces the duplication you adopted the bundle to kill.

Twig: render lists from config

Where a Twig extension is registered (see Templating/), use it for selects and badge lists rather than hard-coded {% for %} arrays:

{{ dictionary('civility') }} {# if a function exists in your version #}

Because the Twig adapter's function/filter naming has changed across releases, confirm it once via bin/console debug:twig | grep -i diction and standardise on that in your templates. The repo also ships bin/lint-twig, which is worth wiring into CI if you template-render dictionaries heavily.

Fixtures / seed data

The Faker/ adapter lets fixtures pick a valid value from the same source of truth, which prevents "fixture value passes locally, fails the production constraint" bugs:

$faker->dictionary('gender');   // verify provider registration in services.yaml

If the provider is not registered in your setup, seed with $faker->randomElement($collection->get('gender')->all()) instead — same consistency benefit, no coupling.

Debugging: use the data collector

A web profiler panel (DataCollector/) is registered. Every request shows which dictionaries are defined and which were touched — this is your fastest answer to "why is the choice list empty in prod?" (usually: dictionary never accessed, or a lazily-backed one resolved to nothing).

Dynamic dictionaries

For lists that cannot live in YAML (DB-backed, translatable, tenant-specific), declare a callable/iterator-backed dictionary and let the container resolve it lazily. Pattern:

knp_dictionary:
    dictionaries:
        country:
            type: callable      # confirm exact keys in Configuration.php
            service: App\Dictionary\CountryProvider
            method: getCountries

Keep the provider cheap and side-effect free; it runs on first access, not at container compile time.

Gotchas and Tips

Laziness is a contract, not an implementation detail. The project's own anti-pattern list states callable and iterator-backed dictionaries must not be eagerly evaluated. Practical consequences:

  • Do not call all() in a constructor, a container compiler pass, or a kernel.request listener "just to warm it up."
  • Do not assume count() is free on such dictionaries.
  • Do not serialise a lazily-backed dictionary into cache before its first access; you may cache an unhydrated wrapper.

Named autowiring changes the DI story — but only where you verify it works. The v3.6.0 "named dictionary autowiring" feature is new; the service-ID format and collision behaviour (two dictionaries sharing a class type hint in one constructor) are worth a smoke test before you rely on them in a large module. If you hit a collision, fall back to Collection.

Do not assume a documented DI tag is consumed. This is an explicitly flagged anti-pattern in the repo. If you plan to plug your own dictionary implementation in via a tag, first grep for the compiler pass that reads it, then write an integration test that proves the tag takes effect. Tags without a consumer fail silently, which is the worst failure mode for a config-driven list.

Service IDs are FQCNs and most adapters are private. Outside the new named aliases, there is no broad autowire/autoconfigure resource block in services.yaml. If you are used to snake_case aliases (knp_dictionary.dictionary.gender), check before relying on them. Type-hint Dictionary\Collection, or the newly added named alias, and add explicit services.yaml wiring or a public alias for anything else.

Compiler pass order is significant and fragile. The bundle's own conventions warn against reordering compiler passes or tagged factory definitions — and v3.6.0 adds a new pass for named autowiring, so the order is now more, not less, load-bearing. If you are debugging a "dictionary not found" error, trace construction order rather than moving passes around; reordering may fix your case and break the framework adapters that consume the same collection.

Custom dictionary implementations are now documented. v3.6.0 ships a dedicated docs page for the custom-dictionary path (PR #371). Before writing a bespoke DictionaryInterface implementation, read that page and cross-check DependencyInjection/Configuration.php — the config shape and factory-tag contract now have a documented reference point, which removes the "guess from src/" tax that existed previously.

Keep config keys stable across environments. A dictionary name is an implicit string key referenced from forms, constraints, Twig, fixtures and now autowiring aliases. Renaming gender → sex in config is a silent break of every one of those call sites. Wrap lookups behind a small App\Dictionary\Names constant class if you expect churn, and add a test that asserts the expected set of dictionary names exists (this also catches a config file that failed to load on a given env).

Watch the labels-vs-values question early. Most teams eventually need human-readable labels different from stored values (e.g. mr → "Mister"). Decide the representation in the dictionary itself (keyed arrays vs. plain lists) before you have thirty call sites, and check how the form adapter and validator treat keys — the two are not guaranteed to agree.

Debugging checklist, in order:

  1. debug:container for Collection, Factory and the named alias — is anything wired?
  2. debug:config knp_dictionary — did your YAML survive normalisation? (The Configuration.php tree normalises entries; malformed shapes frequently become empty dictionaries rather than errors.)
  3. Profiler dictionary panel — was the dictionary accessed at all on this request?
  4. For lazy entries: assert the backing provider returns a non-empty iterable in isolation.

Extension points, ranked by cost. Prefer, in order: config-only dictionaries → callable-backed config dictionaries → named autowiring or a thin wrapper around Collection → a custom Twig/Form type → a custom Dictionary implementation registered via the factory. Steps two through four are the ones most teams land on; anything past that should be backed by an integration test, because you are now outside what CI exercises.

Ecosystem and maintenance signal. The package is MIT, low-star and (in this dataset) has zero listed dependents; its release cadence is steady, v3.6.0 adds real features and a new maintainer (@ErwannRousseau, first contribution), and the CI matrix is current (Symfony 7.1, PHP 8.4/8.5). The maintainership is still small — budget for reading src/ and the docs (now improved) rather than expecting quick upstream answers. That said, the scope is narrow and the abstractions are simple, so the payoff is real for teams with many repeated enumerations: one config file, one injection point, one source of truth across forms, validation, Twig and fixtures. If you have fewer than roughly three duplicated lists, the added dependency and private-collection wiring cost probably outweighs the benefit; build a small enum-backed provider instead.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect