lst/settings-bundle
Laravel settings bundle for centralizing application configuration. Store and retrieve settings through a simple API, keep values organized, and manage options without scattering config across your codebase. A lightweight foundation for consistent runtime settings.
Architecture fit: the package does not fit the stated stack, and this is the dominant finding. lst/settings-bundle is a Symfony bundle — it ships a bundle class, a DI extension, a configuration tree, and Doctrine ORM entity/repository mappings. It has no Laravel service provider, no facade, no publishable config, and no Eloquent model. A Laravel application cannot composer require it into a working state; the Symfony kernel is what instantiates and wires it. So the honest TPM framing is: we are not evaluating a Laravel package that stores settings — we are evaluating a reference implementation of a pattern written for a different framework. The pattern (flat key/value rows, type-aware retrieval, defaults on miss) is sound and worth copying. The artifact is not adoptable as-is.
What the package actually delivers. A small surface area: persist settings rows (key, value, type) through Doctrine and read them back with typed getters and fallbacks. That is the whole value proposition — boilerplate removal for a well-understood problem. Notably absent from the description: caching, encryption at rest, namespacing/tenant scoping, audit history, and any admin surface. Those are the parts that consume the real engineering effort in a settings feature, so the package solves the easy 20%.
Integration feasibility, ranked. (1) Reimplement in Laravel, ~1–2 days. An Eloquent model, a SettingsManager bound in a service provider with get/set/forget/all, type coercion on read, and a cache decorator. This is unambiguously less work than any adoption path, and it inherits our migrations, queues, cache, testing conventions, and observability for free. (2) Run Doctrine inside Laravel. Technically possible via a Doctrine ORM bridge, but it introduces a second ORM, a second migration tool, and a second entity-mapping mental model for one table. Cost is wildly disproportionate to the feature. (3) Port the bundle to Laravel. Only rational if we intend to publish and maintain it publicly — a product decision we have no reason to make.
Technical risk: high, and concentrated in supply chain rather than code quality. Zero stars, zero dependents, zero known consumers. For a 300-line utility that is not automatically disqualifying, but it means no third-party security review, no downstream bug discovery, no issue-response expectation, and no ecosystem pressure to patch a CVE. We would be the largest consumer by an order of magnitude, which in practice makes us the maintainer. For a table that will hold feature flags, thresholds, and potentially integration credentials, that is an undefended position. Secondary risk: the source is unreviewed by us, and Doctrine ORM 2→3 is a breaking migration that a low-traffic bundle may not have tracked.
Key questions to resolve before spending more time.
get() hit the database every call, or is there a cache layer? Settings are read on nearly every request.schema:update.Stack fit. Against a Laravel/Eloquent codebase, fit is poor and the correct disposition is "design reference, not dependency." Read the repository, extract the entity shape and the type-coercion semantics, then build the Laravel-native equivalent. If we ever acquire a Symfony service, the calculus flips and direct installation becomes a modest composer require plus bundle registration — worth noting for organizational consistency, but do not plan around it.
Migration path (Laravel, recommended).
settings table: key (unique, indexed), value (text/JSON), type, group/namespace, is_encrypted, updated_by, timestamps. Scope columns are cheap now and expensive to backfill later.SettingsRepository for persistence; SettingsManager as the public API with get/set/forget/all, coercing on read according to a small, tested type enum.CachedSettings decorator over the manager backed by our shared cache, with write-through invalidation. This is not optional.SettingsServiceProvider; prefer constructor injection over a global settings() helper so the dependency stays visible and swappable.Migration path (Symfony, if it ever applies). Install, register the bundle, generate the table via doctrine:migrations:diff rather than schema:update so the schema enters version control. Immediately wrap the bundle's manager behind our own interface — never let bundle types leak into domain code. That wrapper is the difference between a two-week swap and a two-month one when the package dies.
Compatibility gates. PHP version against our runtime matrix; Symfony/Doctrine majors against anything we run; DB driver behavior for JSON and boolean columns across MySQL, Postgres, and SQLite (they diverge meaningfully); and table/entity name collisions with existing schema. If we reimplement, these collapse to our own framework's support matrix, which is a further argument for reimplementing.
Sequencing.
null, "", "0", "false", and JSON arrays.Avoid: resolving settings eagerly at container boot or in a provider's register(). That converts a database blip into a total application outage.
Maintenance. The code footprint is trivial; the ownership question is not. With zero dependents there is no upstream momentum to ride, so adoption realistically means forking on day one, pinning a commit, and carrying a patch set — a recurring few hours per quarter for dependency bumps and security scanning, plus a named owner. Reimplementing in Laravel reduces this to ordinary in-house code maintenance, which is strictly better on every axis we care about.
Support. No vendor, no SLA, no paid tier, no community corpus. Support is whoever on the team read the source. That is acceptable for a settings table and unacceptable if the same pattern is extended to secrets management. Keep the blast radius to non-critical configuration and write down that boundary.
Scaling. Settings are read-heavy and write-rare — an ideal cache target. With a cache layer this scales to any request volume trivially. Without one, every request pays a database round trip and a global settings table becomes a hot spot that is difficult to shard precisely because it is global. Multi-node invalidation is the real hazard: write-through to shared cache is fine, per-node file caches will diverge and produce "works on one worker" bugs that are expensive to diagnose. Row growth is bounded at hundreds of rows — but only if writes are admin-driven; any code path writing settings per request or per user converts a config table into an unbounded event log.
Failure modes to design against. Database unavailable at boot (resolve lazily, fail soft to defaults where safe); cache stampede after invalidation or deploy (TTL jitter plus a repopulation lock); type coercion drift ("false", "0", "", null need defined, tested semantics — the most likely source of silent production bugs); schema drift if installed via schema:update rather than migrations; plaintext secrets, which we should forbid outright until encryption exists; and abandonment, mitigated by pinning and keeping the integration surface behind our own interface.
Ramp-up. Low in absolute terms, higher than the star count suggests. A new engineer can read the whole package in an afternoon; the ramp-up cost is less "understand this code" and more "understand why we own a dependency nobody else uses." That explanation needs to be written down at adoption time, or the next maintainer will spend a sprint rediscovering it. Reimplementation eliminates this cost almost entirely: the code is ours, follows our conventions, and is discoverable by anyone who knows our stack.
TPM recommendation. Use the repository as a design reference, not a dependency. Deliver a Laravel-native settings module inside two weeks, behind an interface, with caching and an admin surface — and revisit lst/settings-bundle only if a Symfony service enters the portfolio.
How can I help you explore Laravel packages today?