zendframework/zend-config-aggregator
Zend Config Aggregator collects configuration from multiple sources—PHP arrays, ConfigProvider classes, and globals—into a single merged array. It supports caching and recursive merging, making it easy to compose modular application configuration in Zend/Laminas applications.
This is a Laminas/Zend Framework package, but it works fine as a standalone Composer dependency inside a Laravel app when you need layered, mergeable configuration (e.g. feature flags, environment-aware settings, or plugin registries that would otherwise bloat config/).
composer require zendframework/zend-config-agregator
First place to look: the ConfigAggregator class and the bundled providers (PhpFileProvider, ArrayProvider, GlobFileProvider). A minimal first use case in a Laravel service provider:
use Zend\ConfigAggregator\ConfigAggregator;
use Zend\ConfigAggregator\PhpFileProvider;
$aggregator = new ConfigAggregator([
new PhpFileProvider(base_path('config/features/*.php')),
new PhpFileProvider(base_path('config/features/{env}/*.php'), app()->environment()),
]);
$features = $aggregator->getMergedConfig();
Bind that array into the container or expose it via a Feature facade. Caching is the second thing to look at — call $aggregator->getMergedConfig() once and cache via ConfigAggregator::CACHE_FILEMODE when generating the cache file.
Layered config with glob providers. The common pattern is a base directory plus an environment overlay:
new ConfigAggregator([
new PhpFileProvider(base_path('config/app/*.php')),
new PhpFileProvider(base_path('config/app/'.app()->environment().'/*.php')),
]);
Providers are merged in order, later files win. This maps cleanly onto Laravel's env-specific config idea without abusing config/*.php.
Caching in production. Pass a cache file path as the third argument (or via the ConfigAggregator::CACHE_FILE option) so the aggregation work only happens on deploy. In a service provider:
$aggregator = new ConfigAggregator($providers, storage_path('framework/cache/config-aggregator.php'));
$config = $aggregator->getMergedConfig();
New in 1.2.0: set the octal file mode for that generated cache file via the ConfigAggregator::CACHE_FILEMODE option — useful when the web user and CLI user differ and you need group-readable cache files (0640) rather than the default.
$aggregator = new ConfigAggregator(
$providers,
storage_path('framework/cache/config-aggregator.php'),
null,
[ConfigAggregator::CACHE_FILEMODE => 0640]
);
Wrap in a custom provider. For anything dynamic (DB-driven settings, feature flags from a table), implement ConfigProvider and return an array from __invoke(). This keeps the aggregator's merge semantics while letting you source data however Laravel needs.
Wire into a singleton binding. Register the aggregator in a service provider with $this->app->singleton(...) and resolve the merged config once. Avoid constructing the aggregator per-request unless you're caching.
Zend\ConfigAggregator. If you're on a newer Laminas stack elsewhere, remember it's Laminas\ConfigAggregator — don't mix imports.array_merge_recursive blindly — the aggregator delegates to Zend\Stdlib\ArrayUtils::merge, which has specific rules for numeric keys (lists are appended, not replaced). Test any array-of-lists config carefully.0600/0640 as octal literals, not the string "600". Writing 600 (decimal) will give you unexpected permissions. Also note the cache file is written by whatever user runs the process — on Laravel deploys that's often www-data in prod and your CI user locally, so mismatched ownership will bite you before the mode does.env(): aggregation happens at boot, so any env() calls inside provider files need to happen before the cache is generated. If you change APP_ENV, clear the aggregator cache file.ConfigProviderInterface, plus ConfigAggregator::addProvider() for appending at runtime (e.g. from a package's service provider) — handy for package-level config registration without forcing a rebuild.var_export($aggregator->getMergedConfig(), true) into a scratch file is the fastest way to see what actually won the merge when a Laravel-app-adjacent config value looks wrong.How can I help you explore Laravel packages today?