Skip to content

DI: one source per key ​

yiisoft/config refuses to merge two vendor packages that define the same key in the same group — it throws Duplicate key "<class>". The family is built around that rule instead of fighting it: every key has exactly one owner.

What the core binds ​

KeyBound by the coreWhy
AbTestingyesthe facade
AssignmentStrategyyesWeightedHashAssignmentStrategy, the only algorithm
ExperimentProvidernothe experiment source — owned by the application or a storage package
ExposureTrackernoan event sink — owned by the application or one adapter
ConversionTrackernoan event sink — same

AbTesting requires an ExperimentProvider and has no default, so without a binding it does not resolve. That is intentional: two sources of experiment truth is the bug, and a missing one should fail loudly.

The trackers do have defaults — the no-op NullExposureTracker and NullConversionTracker — so a fresh install runs and records nothing.

Who owns what ​

SetupExperimentProviderTrackers
Core onlyyour app → ConfigExperimentProvideryour app → Logger* sinks, or nothing
+ yii3-ab-testing-dbthe db package → DbExperimentProviderunchanged
+ yii3-ab-testing-outboxunchangedthe outbox package, or your app
Both sinks at once—your app → CompositeExposureTracker / CompositeConversionTracker

yii3-ab-testing-clickhouse binds nothing since 2.0. It used to bind both trackers, which made installing it next to the outbox adapter a Duplicate key error.

The two mistakes ​

A storage package plus a manual binding. Installing yii3-ab-testing-db and binding ConfigExperimentProvider in config/common/di/*.php is the same conflict as two vendor packages: delete the manual binding when the storage package arrives.

Two adapters for one sink. If two installed packages both bind ExposureTracker, pick one, or compose them once in your application — the composite is the only place fan-out belongs.

Overriding in the application ​

The application layer may override a vendor binding; that is how you replace an adapter's default tracker with a composite. The override lives in your config/common/di/*.php, and it is the only binding of that key you write.