laminas/laminas-config-aggregator
A Laminas package that aggregates configuration from multiple sources—PHP files, arrays, and modules—into a single merged config, with support for caching, glob patterns, and post-processing hooks for use in Laminas and Mezzio applications.
Architecture fit. laminas-config-aggregator is a framework-agnostic, single-purpose library: you construct ConfigAggregator with an ordered list of providers, optionally a list of post-processors and a cache path, and call getMergedConfig(). Merging is shallow-by-key with later providers winning; recursion is not deep-merged across nested arrays. Providers are pluggable (PhpFileProvider for *.php returning arrays, LaminasConfigProvider for YAML/JSON/XML/INI via laminas-config), and post-processors are plain callables or invokable classes that transform the merged array (e.g. parameter/placeholder resolution à la Symfony ParameterBag).
It has no coupling to Laravel — no service provider, no facade, no Illuminate\Config\Repository awareness. That cuts both ways: it drops in cleanly, but it is not an extension of Laravel's config system, it is a competing one. Architecturally it fits Laravel as an alternate composition layer that feeds config(), not as a replacement for the repository contract.
Where it genuinely fits a Laravel stack:
.env. If product/ops wants YAML config authored by non-PHP teams, this is the shortest path.config/*.global.php and a deterministic precedence order matters.%database.host% resolved after merge, without hand-rolling it.Where it does not fit. ~80% of a typical Laravel app's config needs are already served by config/*.php + php artisan config:cache. Introducing a second aggregation stage for a standard app buys complexity and a second precedence model for near-zero gain.
Integration feasibility. High. Composer install, one class, no bootstrapping ceremony. The only real work is an adapter: a ConfigAggregatorServiceProvider that runs the aggregator once and writes the result into the container/Config\Repository, plus reconciliation with config:cache. Effort is measured in days, not weeks.
Technical risk. Low at the code level, moderate at the ecosystem level:
@final (since 1.18.0): treat as non-extensible; compose, don't subclass.CACHE_FILEMODE (octal, e.g. 0600) — a permissions/ops concern that must be owned.PhpFileProvider globbing depends on laminas-stdlib's glob compatibility layer (historically fragile on FreeBSD/IBM i) — irrelevant on standard Linux containers, relevant if you have exotic build hosts.Key questions to resolve before commitment:
Stack fit. Treat the aggregator as a build-time composer of config, not a runtime service. Recommended topology:
ConfigAggregator instantiated once at bootstrap (or in a dedicated config:aggregate artisan command), producing a single PHP array or cache file.Illuminate\Config\Repository via $app['config']->set(...), so application code keeps using config('...') unchanged. Developers never see the new library.getMergedConfig() from request-handling code paths — cache or memoize.Migration path (incremental, reversible).
config:cache output in CI. Any drift is a bug in the migration, not in production. Exit here if the diff is not clean and boring.config/ directory.Compatibility notes.
laminas-config is only required if you use LaminasConfigProvider (YAML/JSON/XML/INI); skip it for PHP-only.brick/varexporter is a transitive concern for cache export — its version range has churned (^0.4 || ^0.5 || ^0.6); do not pin tightly.Sequencing.
laminas-config-aggregator, laminas-stdlib, optionally laminas-config, brick/varexporter).config:aggregate command; wire into CI shadow diff.php artisan config:cache serialize the final array — one artifact, one invalidation story. Use the aggregator's cache only for non-Laravel consumers.Maintenance. Low ongoing code maintenance — the library is small and stable, and releases are overwhelmingly automated dependency bumps. The real maintenance cost is team cognition: engineers must understand a precedence chain that isn't Laravel's. Budget for documentation and a "config lives here" rule enforced in onboarding. Version upgrades are predictable (semver, minor releases), but the annual PHP-support window shifts (8.0 dropped in 1.14, 8.1 in 1.19) — schedule the runtime upgrade review as a recurring task, not an incident.
Support. Primary support is upstream docs at docs.laminas.dev plus the Laminas GitHub issue tracker — no commercial backing, no SLA. With 0 tracked dependents and ~43 stars, expect to be close to the only ones in your niche; you are effectively self-supporting. Mitigate by keeping the integration surface to a single provider class (~100–150 LOC) so replacement is cheap.
Scaling. Negligible runtime cost: aggregation is array merges over a handful of files, and the cache path makes bootstrap a single include of an exported PHP array — opcache-friendly and faster than repeated glob+merge. Costs scale with number of config fragments and post-processors, not traffic. If you use providers that hit the filesystem with globs on every request, you have a design bug — always cache. Memory footprint of the merged array is the same order as Laravel's native config.
Failure modes to design against.
config:cache) and never shipping a cache file in the artifact.CACHE_FILEMODE (e.g. world-readable 0644 for a file containing secrets) is a real security exposure. Default to 0600, and keep secrets in env/secret manager rather than in aggregated files.Ramp-up. One to two engineer-days for a senior PHP developer to become productive; the API is a constructor plus one method. Ramp-up cost is concentrated in policy, not code: which sources exist, what order they run in, and where the cache is written. Write that down before writing any code.
TPM verdict. Adopt only against a named requirement (multi-format config, cross-framework parity, or fragment ordering), and only as a build-time composer feeding Laravel's native config repository. Do not adopt for a conventional Laravel application — Laravel's own config pipeline is more familiar, better supported, and cheaper to operate. Keep the integration to a single adapter class so that the dominant risk — a low-adoption dependency sitting in the highest-blast-radius position in the stack — remains a two-day swap rather than a migration.
How can I help you explore Laravel packages today?