pharaonic/laravel-settings
A Laravel package for managing application settings through a simple, flexible API. Persist configuration values in the database, retrieve them easily at runtime, and organize settings cleanly for small apps or larger projects that need centralized control.
What it is. A thin key/value settings store: one publishable migration, a service-provider binding, a facade, caching, and type-aware get/set helpers, plus a "settings for model objects" mode (almost certainly a polymorphic relation, though the assessment notes never confirm the schema — verify against the migration before assuming).
Architecture fit. This is cross-cutting infrastructure, not domain logic. It belongs in the same layer as config() — read by application services and delivery code, owned by no bounded context. That makes the fit easy but also low value: anything you gain by adopting it, you can replicate with a ~150-line model + cache decorator. There is no architectural lock-in, which is the single most important fact for a TPM decision here.
(model_type, model_id, key) is a query-storm waiting to happen at scale.Integration feasibility. High on the technical axis, moderate on the lifecycle axis.
| Factor | Assessment |
|---|---|
| License | MIT — clean, no legal review needed |
| Dependencies | Laravel/Illuminate only; no external services, no daemons |
| Install surface | Composer + one migration + optional config publish |
| Data footprint | One table; reversible with a rollback migration |
| Exit cost | Low — read the source, wrap behind your own interface, replace later |
| Version currency | Last release 2022-12-14; Laravel 11/12 support is unverified |
| Community | 7 stars — effectively no third-party review, no CVE eyeballs, no issue triage |
Technical risk (ranked).
composer.json constraints will tell you instantly whether it installs on your target version; the real question is whether it works on it. No maintainer cadence means no fix if it doesn't.null instead of your intended default, or a driver/config fallback that resolves to something you didn't choose. Requires an explicit boot-time assertion test.set() invalidate the cache? Across queue workers, multiple app servers, and Octane? If the cache is process-local or per-instance, you get split-brain settings in production — the worst kind of bug because it's intermittent and environment-dependent."false" are truthy in PHP; arrays stored via serialize() are unreadable by other tooling. Pin this down with a round-trip test per type.$_GLOBALS unless governed. That's an organisational risk you're importing, not a package defect — but it lands on your plate.artisan tinker access. If a setting controls behaviour (rate limits, feature exposure, outbound endpoints), that's a privilege-escalation path with no audit trail.Key questions to resolve before committing.
composer.json allow the Laravel and PHP versions we actually run? Test on the newest supported, not the oldest.config:cache?get() return for a missing key, and can we inject a default without ambiguity with a stored null?Stack fit and the alternative. For a Laravel monolith or a small service, this fits without ceremony. But be honest in the comparison: spatie/laravel-settings is the de facto standard — maintained, typed settings classes, strong static analysis story — at the cost of more ceremony and a per-class codegen pattern. pharaonic/laravel-settings wins only if you want a loose key/value bag with zero class scaffolding. If your team wants typed, greppable, refactor-safe settings, this is the wrong tool and the decision will be regretted in twelve months when the settings table has 400 undocumented keys.
Choose this package when: the settings are few, operational, and genuinely dynamic. Otherwise choose a typed alternative or a JSON column on your own model.
Migration path (adopt).
composer require pharaonic/laravel-settings:<exact> in a branch. No caret range until you've verified the constraint resolves on your Laravel version.vendor:publish the config and migration; commit both. The published config is the real documentation for a package with thin docs.down()) is safe to run and that the migration filename won't collide with existing settings tables.App\Contracts\SettingsRepository with get/set/forget and a typed accessor layer on top. Bind the package facade behind it. No Facade calls in services, jobs, or listeners — delivery layer only. This is the seam that makes the exit cheap and testing clean.Cache::remember in the adapter with explicit invalidation on set(). Own the invalidation; don't inherit it.Compatibility work.
php artisan config:cache and under Octane if you use it — check the provider for singleton() bindings holding request state and re-register as scoped() in your own provider if so.Sequencing (six weeks, part-time).
| Phase | Work | Exit criteria |
|---|---|---|
| 1 (wk 1) | Spike: install pinned, publish, migration, read-only path in a throwaway branch | Round-trip test passes for every supported type |
| 2 (wk 2) | Adapter + contract + key registry + boot assertions | Domain code compiles with zero facade references |
| 3 (wk 3) | Migrate tier 1 values (non-critical operational flags) from env/hardcoded | Feature-flagged; flag defaults to old source of truth |
| 4 (wk 4–5) | Run shadow: write to both, read from old, log divergence | One clean release cycle, zero divergence |
| 5 (wk 6) | Flip flag, document runbook, publish exit plan | On-call can restore previous behaviour from a single commit |
Do not, in v1: use the model-scoped settings feature, put secrets in the table, or make the write path reachable from a user-facing form without authorization and validation in front of it.
Maintenance. The package is effectively frozen. Budget for owning it rather than depending on it: assume you will fork or reimplement within 18–24 months, most likely triggered by a Laravel major upgrade. Reimplementation cost is a day or two of engineering — that's the number that makes this decision cheap. Do not, however, let "it's only 150 lines" become an excuse to skip the wrapper; the wrapper is what makes the reimplementation a day instead of a sprint.
Support. No vendor, no maintainer cadence, no community. Name a single internal owner in the ADR. If nobody volunteers, that's your answer on adoption.
Scaling characteristics.
array or file, in any multi-instance deployment.octane:warm or a boot-artisan command) or accept the cold-start burst knowingly.Failure modes to instrument.
| Failure | Symptom | Mitigation |
|---|---|---|
Missing key → null |
Feature behaves as disabled; not obviously a bug | Registry with required keys; throw in non-prod; assert on boot |
| Stale cache after write | Setting changed but behaviour doesn't | Own the invalidation; log every set() with old/new |
| Cross-worker inconsistency | Intermittent, environment-dependent behaviour | Shared cache store; verify in a 2-worker staging test |
| Type drift | Boolean "false" evaluates truthy |
Typed accessors only; no raw get() outside the adapter |
| Provider/version mismatch after upgrade | Hard failure at boot — actually the good case | Pin exact versions; upgrade in branch; artisan about check |
| Octane state leakage | Values bleed between requests | scoped() binding in your provider; add to Octane flush list |
| Plaintext secrets in DB | Credential exposure in backups/reads | Policy: secrets stay in env/secret manager, always |
Ramp-up. Very low developer friction — an engineer can use it in under an hour. That's also the risk: the API is simple enough to be misused casually, and the cost lands on operations. Counter it with three artefacts: (1) the key registry as the single source of truth, (2) a short ADR stating what this package is not for (secrets, authz, audit-sensitive values), and (3) a code-review rule that raw facade calls are rejected outside the delivery layer.
Overall posture. Conditional, low-stakes adoption. The blast radius is small, the exit cost is low, and the capability is commodity — so the decision is defensible even with a stale dependency. Gate it on three things: it installs and passes a round-trip test on your target Laravel version, the wrapper and key registry exist before any domain code touches it, and someone's name is on the ADR as owner. If any of those three fails, write the 150 lines yourself and skip the dependency entirely.
How can I help you explore Laravel packages today?