yiisoft/config
Yii config helps assemble and merge application configuration from multiple sources, with support for config groups, defaults, environment-specific overrides, and DI definitions, making Yii setup organized and predictable.
Think of yiisoft/config as Laravel's config repository plus a package-discovery mechanism, inverted: instead of the app defining all config and packages publishing into it, every package ships its own config/ directory, and the root app merges them into groups (params, di, common, or any custom group).
Minimal setup:
composer require yiisoft/configconfig/params.php and config/di.php in your app — both plain PHP files that return arrays (no config() helper, no facades).config/config-providers.php listing the provider classes you want merged (each package ships one; check its config/ directory).Config instance once, early:use Yiisoft\Config\Config;
$config = new Config(
rootPath: dirname(__DIR__),
configDirectory: 'config',
vendorDirectoryPaths: [dirname(__DIR__) . '/vendor'],
cacheFilePath: dirname(__DIR__) . '/runtime/config-cache.php',
);
$config->get('params')['app']['name'] or $config->get('params', 'app').First use case: replacing config/app.php. Put values in config/params.php, read them from factories or bootstrap instead of calling env() anywhere.
Look first at: config/config-providers.php, the merge order (root wins over vendor?), and the docs on DI merge rules — those rules are where most of the learning curve is.
Package/module config provider. A reusable package should ship its own config/params.php, config/di.php, and a provider class registered in the consumer's config/config-providers.php. That's the equivalent of Laravel's mergeConfigFrom() + service provider, but automatic and uniform across packages.
Group-based split. Keep di.php for container wiring (bindings, interfaces, constructor args) and params.php for scalar/array settings. Anything that isn't DI belongs in common.php or a custom group. Don't mix them — the merge semantics differ per group.
DI definitions use arrays with method-style keys, which feels like Laravel's container bind/singleton mapped into data:
// config/di.php
return [
LoggerInterface::class => [
'class' => Logger::class,
'__construct()' => [Reference::to('log.target')],
'setLevel()' => ['debug'],
],
];
Config modifiers/plugins. Use built-in modifiers (e.g. ReverseMerge, RemoveKeys, ExtractArrays) or write your own plugin implementing the package's modifier interface to post-process a group. This is where you implement env-var interpolation, strip dev-only keys in production, or reshape vendor params before your app reads them.
Caching in the bootstrap path. Pass cacheFilePath in staging/production and skip it locally. Rebuild on deploy by deleting the cache file — treat it exactly like php artisan config:cache / config:clear.
Bridging into Laravel. If you're introducing this into a Laravel app (rather than a Yii app), the pragmatic integration is a service provider that instantiates Config once, then pushes each group into Laravel via config(['yii.'.$group => $value]). That keeps the rest of the app idiomatic while reusing vendor packages that assume yiisoft/config.
Typical workflow: add a setting in the owning package's params.php → register the provider → read via Config::get() in a factory → clear cache → deploy.
config/*.php won't take effect until you delete the cache file. If a change "does nothing," clear it first.config/config-providers.php generally override earlier ones, and the root app is meant to win over vendor. Always verify precedence for the specific group rather than assuming Laravel-like "app wins" behavior.params and di merge differently. params is a deep recursive merge; di has special-case rules (nested constructor args, $-prefixed keys, set()/with...() method calls, append/prepend semantics). A definition that looks right for params may silently mutate in di.env() helper. Files are plain PHP — use $_ENV/getenv() or inject a Environment value. Environment-substitution is not automatic for params; wire a modifier if you want Laravel-style env('FOO', default) semantics.yiisoft/di (or your own container) consumes them. Don't expect Config::get() to resolve services.$config->get('params') and compare with the individual provider files to find what overrode what. Grep the vendor config/ directories for the key — the source of an unexpected value is usually a package default, not your code.cacheFilePath at a directory your process can write to (e.g. runtime/), or omit it. A silent write failure makes you think caching is on when it isn't.composer update; always contribute through a provider or a modifier.params.php clean and avoids repetition across providers.How can I help you explore Laravel packages today?