Use ConfigAggregator for modular Laminas config with safe environment overrides
Decision guide for composing Laminas MVC config with ConfigAggregator versus manual merge or .env-only. Covers merge order, caching, deploy safety and a concrete provider pattern.
14 Feb 2026, 03:28 UTC

The decision: modular config with safe environment overrides
You have a Laminas MVC application with several modules, each shipping defaults, plus environment‑specific values for database, mailer and feature flags. The useful takeaway is to adopt Laminas ConfigAggregator with discrete providers per module instead of manual array_merge in Module.php. It gives deterministic merge order, deep merge of nested service config, and a production config cache that can be invalidated without code changes.
Constraints that drive the choice are modularity, environment overrides, and deploy safety. Module defaults must not be silently overwritten, local overrides must win, and a cached merged config must be usable in production without touching bootstrap code.
Options compared
| Option | Merge behavior | Environment overrides | Cache support | Typical risk |
|---|---|---|---|---|
| ConfigAggregator with providers | Provider ordering + deep merge via Laminas\\Config\\ConfigAggregator | Natural via autoload file naming, e.g. config/autoload/*.local.php | Yes, merged config can be written to a cache file and loaded by ServiceManager | Bootstrap cost and need for disciplined provider names |
| Manual array_merge in Module.php | Shallow or manual deep merge you implement | Possible with conditionals, easy to get wrong | No built‑in cache, you must implement it | Merge order bugs on key collisions, changes require code edits |
| .env-only config | Flat key/value, no nested structures | Good for secrets | No | Unsuitable for complex service factories and nested config |
Trade‑offs that matter in production
ConfigAggregator adds a small bootstrap cost and requires provider naming discipline, but it makes merge order explicit. Providers are evaluated in the order you list them, later providers override earlier ones for the same keys. Deep merge semantics for nested arrays are version‑sensitive across laminas/config major versions, so verify the behavior in your installed version before relying on it.
Manual merge is transparent and has zero extra dependencies. It is error‑prone on collisions and does not provide caching. Changes to config often require a code change and redeploy, which reduces deploy safety.
.env-only keeps secrets out of code but cannot express nested service config, factories, or module defaults. It is best used as a complement, not a replacement.
Concrete implementation pattern
Register ConfigAggregator in your application Module and point it at autoload providers. The following sketch shows the structure, not a tested deployment.
// module/Application/Module.php
namespace Application;
use Laminas\\Config\\ConfigAggregator;
use Laminas\\ModuleManager\\Feature\\ConfigProviderInterface;
class Module implements ConfigProviderInterface
{
public function getConfig()
{
$aggregator = new ConfigAggregator([
// global defaults
Laminas\\Config\\ConfigProvider::class,
// module configs
Application\\ConfigProvider::class,
// autoload files
Laminas\\Config\\Aggregator\\PhpFileProvider::class . '::' . __DIR__ . '/../../config/autoload',
], __DIR__ . '/../../data/cache/app_config.php');
return $aggregator->getMergedConfig();
}
}
Place module defaults under config/autoload/global.php and environment overrides under config/autoload/local.php or config/autoload/{env}.php. Provider ordering controls precedence: global defaults first, module defaults next, environment files last.
Config cache file path requires write permission for the web server user in production. Do not enable caching in development; stale merges cause silent config drift. A practical way to check the result is to request the Config service from ServiceManager and inspect the merged keys:
// run in a CLI script under the application root, user with read access to vendor and config
$serviceManager = $app->getServiceManager();
$config = $serviceManager->get('Config');
// inspect $config['db'] or other keys
Expected check is that values from later providers override earlier ones and nested arrays are merged according to the installed laminas/config version.
Validation and limitations
- Check composer.lock for laminas/config version and inspect vendor/laminas-config/src/ConfigAggregator.php to confirm the provider interface and aggregate method signature you are using.
- Create two config files with overlapping keys and instantiate ConfigAggregator with both providers in a small PHP script to observe merge order. Do not assume deep merge behavior without verification.
- Enable config cache in production and confirm the cache file is regenerated after a config change and that ServiceManager returns the updated Config after cache invalidation.
Limitations: ConfigAggregator merge semantics for nested arrays are version‑sensitive. Config caching must be disabled in development to avoid stale merges. Forgetting to invalidate cache leads to silent config drift. Rollback is to remove the cache file and let the aggregator rebuild on next request, or revert the offending provider file.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.