Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Composite Config Laravel Package

cartalyst/composite-config

Composite Config is a Cartalyst package for merging and layering configuration from multiple sources into a single composite config repository, letting PHP applications organize, override, and resolve settings cleanly. A small utility aimed at developers who need flexible config composition.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package targets a real architectural gap: Laravel's Illuminate\Config\Repository is filesystem-only by default, so any value that must change without a deploy has to be faked with .env manipulation, config-file forks, or a bespoke settings table read at arbitrary depths of the app. A composite repository that layers a database source over the file source and preserves dot-notation access through the standard config() helper is the correct shape of solution. It respects the framework contract (Illuminate\Contracts\Config\Repository) rather than asking application code to call a vendor-specific API, which is the single most important architectural property here — it means the dependency is swappable at the container-binding level. Where it fits well: multi-tenant overrides (branding, limits, locale, integration endpoints), white-label deployments currently maintained as config forks, and settings that today require an .env edit plus a redeploy. Where it does not fit: feature flagging and cohort targeting (Laravel Pennant or a hosted flag service owns that), secrets management (a DB-backed config layer is not a vault, and cannot configure the very database connection it depends on), and anything read before the DB connection exists.

Integration feasibility. Nominally low effort — it's a service provider rebinding config, a publishable migration, and a repository class — but the practical constraints dominate. Last release is December 2020 (v5.1.0, "Add PHP 8 support"); the tag lineage suggests a Laravel 8-era target, and the illuminate/* constraint is unlikely to resolve against Laravel 10/11/12 without patching or a --ignore-platform-reqs leap of faith. There is effectively no README, so the service provider's register() is the documentation. Auto-discovery should not be assumed for a 2020-era Cartalyst package; registration is manual, and provider ordering matters — any package reading config() in its own register() will see the un-composited file repository if it registers first. The license is flagged NOASSERTION (historically Cartalyst's BSD-3 plus a supplemental commercial clause), which must be cleared by legal before it enters a production composer.json — that is a gate, not a footnote.

Technical risk. High, and concentrated on the boot path. The repository queries the database while the container is still booting, so a fresh clone, an unmigrated database, or a CI job without MySQL hard-fails before the app can render an error page. php artisan config:cache is the second major hazard: a cached config file is a flat PHP array produced from the file repository, so DB overrides either get baked in at cache time (stale after every settings change) or are bypassed entirely — both states must be tested explicitly. Type coercion is a live defect source: MySQL returns "1", "0", "null" as strings, and "0" is truthy in PHP, so boolean and array overrides need a JSON column or explicit serializer. Long-running processes (Octane, queue:work, Horizon) read config once at boot, so a settings change has no effect on a running worker until restart. Finally, the support posture is the risk: 0 stars, 0 dependents, one release in 2020, no security-response path, no one to file an issue with. A config layer sits on the boot path of every request — unusually high blast radius for an unmaintained dependency.

Key questions.

  1. What are the declared php and illuminate/* constraints in the package's own composer.json, and does composer why-not cartalyst/composite-config 5.1.* resolve cleanly on our current Laravel version?
  2. Has legal reviewed the actual license text (not the repo metadata) for commercial redistribution?
  3. Is the requirement genuinely static layered values, or is it targeting/rollout? If the latter, this is the wrong tool and the evaluation should stop here.
  4. Does the provider wrap the DB read in Schema::hasTable() and a try/catch fallback to file values, or do we have to add that guard ourselves?
  5. How does the implementation behave with bootstrap/cache/config.php present versus absent?
  6. Does an override row with a null value shadow the file value or fall through to it?
  7. What is the multi-tenant resolution path — does tenant identity resolve before the config repository first reads, and does it come from env/files rather than from config itself?
  8. Build versus buy: the described behavior is a decorator around Illuminate\Contracts\Config\Repository — a few hundred testable lines. Against an abandoned, zero-adopter, license-unclear package, is our own ~50–300 line decorator the lower-risk path? (On the evidence, yes.)

Integration Approach

Stack fit. Only viable if we are on Laravel 8 or earlier with PHP ≤8.0, or willing to vendor/fork the source and patch constraints. On Laravel 10/11/12 there is no evidence of support and the dependency graph will almost certainly fight us. If we are on a modern Laravel line — which most teams evaluating this in 2024+ are — the realistic integration is not composer require but reading the source as a reference implementation and writing our own decorator behind the same contract.

Migration path. Treat the package as transparent, not as an API. Application code must never call CompositeConfig::get(); it must keep calling config('x') and remain ignorant of provenance. Sequence: (1) confirm install and license clearance; (2) publish migration and config; (3) register the provider manually, ahead of any provider that reads config in register(); (4) insert one low-risk override (pagination size, a feature flag, app.name) and verify via tinker that get_class(app('config')) is no longer Illuminate\Config\Repository and that the value actually changed; (5) only then expand scope. Establish the three-layer precedence as an explicit, documented invariant: files hold defaults and shape (version-controlled), the database holds per-environment and per-tenant deltas (deployable, sparse), runtime config([...]) holds request-scoped overrides (tests, tenant context). Write a one-off data migration that imports currently hard-coded values into the overrides table so file values become defaults rather than the live source, and diff the resulting rows against the files before shipping.

Compatibility. Two compatibility fences to design around rather than discover: config:cache semantics, and provider ordering. For caching, the target model is one cache entry per tenant, warmed on write and forgotten on write, not one serialized blob per deploy — which changes deploy topology and is a decision to own explicitly rather than delegate. For ordering, dd(config('your.key')) from inside a competing provider's register() is the cheap confirmation that we see file values where we expect DB values.

Sequencing. Phase 0: license and constraint verification (gate — do not proceed past a fail). Phase 1: single-key read in a non-critical path, on a staging environment, with the table-missing guard in place. Phase 2: cache-mode matrix testing (cache present/absent, cold boot, Octane restart) and type-coercion tests for booleans, arrays, and null-versus-absent. Phase 3: data migration importing existing config values, diffed against files. Phase 4: admin/support write path routed through our own service — never through config([...]), which in most implementations mutates in-memory state only and silently vanishes on the next request. Phase 5 (parallel, low cost): pin the exact version 5.1.0, disable unattended updates, and vendor the source into the repo if it stays.

Build-versus-buy, stated plainly. Read it as a reference implementation rather than a dependency. The behavior is a decorator over the config contract; keeping the schema ours, decorating the contract with our own class, and routing writes through our own service means replacement is one binding swap rather than a call-site hunt. That is the recommended integration posture regardless of whether the vendor package ships.

Operational Impact

Maintenance. This is the dominant cost. There is no maintainer cadence, no issue triage, no security-response path, and no community (0 dependents means nobody else has exercised it in production and reported back). Every Laravel upgrade becomes our audit: the package decorates the config contract, and that contract's surface can shift under us with no upstream fix coming. Mitigation is structural — vendor the source, own the schema, keep the binding swappable — but that mitigation is most of the work of just writing it ourselves.

Support. No SLA, no commercial backing, no escalation. A config layer sits on the boot path of every request; a defect here is a total outage, not a degraded feature. On-call needs a documented debugging ladder: get_class(app('config')) to confirm the swap → Schema::hasTable() to confirm the table → DB::table(...)->get() to confirm the data → toggle config:cache on and off to isolate caching → storage/logs/laravel.log for the exception under the swallowed one. That ladder should be written down before go-live, because the failure symptom ("works in tinker, wrong value in the booted app") is non-obvious.

Scaling. Two scaling dimensions, both manageable with discipline. Query volume: loading overrides lazily per key turns boot into an N+1 query — load the whole override set once, keyed per tenant/connection, Cache::rememberForever(), and forget on write. Budget one query per process boot, not per key. Configuration cardinality: one cache entry per tenant grows linearly with the customer base, so warm-on-write rather than warm-on-deploy, and keep the DB layer sparse — only keys that actually differ — so a missing or empty table is a survivable state rather than a fatal one. Multi-tenancy is the hardest wiring problem: tenant resolution usually wants to read config itself, so keep tenant identity in env/files and tenant settings in the database, resolved before the repository first reads.

Failure modes to design against.

  • Boot-time DB dependency — fresh clone, unmigrated DB, or CI without MySQL hard-fails before the error page renders. Guard with Schema::hasTable() plus a try/catch falling back to file values.
  • config:cache divergence — overrides either baked stale into the cache or bypassed entirely. Test both states explicitly.
  • Provider ordering — a provider reading config in register() before ours sees raw file values.
  • Silent write loss — persisting via config([...]) mutates memory only; the classic QA finding is "the admin settings save worked until the next request."
  • Type coercion — "0" from MySQL is truthy in PHP. Use a JSON column or explicit cast/serializer.
  • null versus absent — depends on implementation; one explicit test settles whether a null row shadows or falls through.
  • Stale long-running workers — Octane/queue/Horizon read config once at boot; a DB change is invisible until restart. Plan a Reload/signal path or document "restart on settings change."
  • Credential creep — APP_KEY, DB hosts, and mail credentials must never enter the override layer; a config override that depends on the database cannot configure the database. Any stored API keys need Laravel's encrypted cast and an authorized admin surface, not a hand-edited row.

Ramp-up. Low for the concept, moderate-to-high for the safe operation. An engineer can get a single override reading in an afternoon; getting the guard rails right — table-missing fallback, cache-mode behavior, type coercion, tenant resolution ordering, write-path routing, Octane restart procedure — is where the days go, and none of that work is documented upstream. Testing strategy should bind a stub Illuminate\Contracts\Config\Repository in setUp() so unit tests never touch a database, and feature tests need RefreshDatabase plus the Schema::hasTable() guard or the boot-time read fires before migrations run. Team familiarity with Laravel's container and config internals is the real prerequisite.

PM recommendation. The capability is worth having; this package is not the way to get it. Score the build path: a decorator around Illuminate\Contracts\Config\Repository that merges a cached DB array over the file array, with our schema, our write service, our guards, and our tests. It is small, fully testable, and it converts an unmaintainable vendor dependency into owned code — with the abandoned package serving its highest-value role as the reference implementation we read before writing it. If the underlying need is actually feature targeting or A/B rollout rather than static layered values, stop and route to Laravel Pennant or a hosted flag service; that mis-scoping is expensive and common.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core