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

L4 Settings Laravel Package

anlutro/l4-settings

anlutro/l4-settings provides persistent key-value application settings for Laravel 4. Store configuration in the database, JSON files, or memory, then read and write values through a simple facade with caching and fallback support for flexible runtime configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer:

composer require anlutro/l4-settings

On Laravel 5.5+ the service provider and Setting facade are auto-discovered. On older versions register them manually in config/app.php:

'providers' => [
    Anlutro\LaravelSettings\ServiceProvider::class,
],
'aliases' => [
    'Setting' => Anlutro\LaravelSettings\Facade::class,
],

Publish the config (and the migration, if you plan to use the database store) and run it:

php artisan vendor:publish --provider="Anlutro\LaravelSettings\ServiceProvider"
php artisan migrate

Where to look first: config/settings.php. It defines a default store and a stores map — typically a json driver writing to storage/app/settings.json and a database driver writing to a settings table. Pick the one that matches your deployment model before you write any application code; switching later means a migration script.

First use case: a tiny admin-editable site configuration, no custom table needed:

Setting::set('site.title', 'Acme Dashboard');
Setting::set('site.theme', ['primary' => '#0a84ff', 'density' => 'compact']);

Setting::get('site.title', 'Untitled');   // "Acme Dashboard"
Setting::get('site.theme');               // ['primary' => '#0a84ff', ...]
Setting::has('site.title');              // true
Setting::forget('site.title');           // removes it

Values are JSON-encoded on write and decoded on read, so scalars, arrays, and booleans round-trip natively. That single feature is usually why people reach for the package over a hand-rolled key/value table.

Implementation Patterns

Namespace your keys. Flat keys age badly. Adopt a convention immediately — site.*, mail.*, billing.* — and treat the prefix as a namespace you can enumerate later. Setting::all() returns a flat key => value array you can filter by prefix to build an admin screen per section.

Wrap the facade in a typed repository. The raw API returns mixed. In real applications you want bool for toggles and string for titles, with defaults centralised in one class:

class AppSettings
{
    public function __construct(private Setting $settings) {}

    public function maintenanceMode(): bool
    {
        return (bool) $this->settings->get('app.maintenance', false);
    }

    public function supportEmail(): string
    {
        return (string) $this->settings->get('mail.support', config('mail.from.address'));
    }
}

Inject this everywhere. It gives you one place to add caching, one place to change key names, and it makes tests trivial to fake.

Seed defaults from a config file, not from code. Keep a config/settings-defaults.php describing every key, its type, and its default. A single artisan command (settings:sync) walks the file and calls has()/set() only for missing keys. This gives you reproducible environments, a schema for the admin UI, and a deploy step that is safe to run repeatedly.

Cache reads deliberately. The database store issues a query per get(). For anything hit on every request (feature flags, theme, locale), load once and memoise:

$all = Cache::rememberForever('settings.all', fn () => Setting::all());

Invalidate in your write path — ideally in the repository's set() wrapper — rather than scattering Cache::forget() around controllers. If you go this route, always read through the same repository so the cache cannot drift from the table.

Build the admin screen as a form over known keys. Iterate your defaults config, render a typed input per entry, then on submit loop the submitted keys and call set(). Cast at the boundary (checkbox → bool, number field → int) so the stored JSON type stays stable. Never accept arbitrary key names from the request body.

Use has() for flags, get() for values. get($key, $default) only falls back when the key is absent. An explicitly stored null (or false, or 0) is still "present" and will be returned. has() is the honest existence check, and it's the one you want for feature toggles where false and "not configured" must be distinguishable.

Testing. Point the default store at a throwaway JSON file in storage/framework/testing/ (or use RefreshDatabase with the database store) and clear it in setUp(). Because the facade resolves a singleton per process, forget the resolved instance between tests if you mutate it — otherwise one test's settings leak into the next.

Gotchas and Tips

The JSON store rewrites the whole file on every write. That is fine for a handful of admin-edited values and genuinely awful for high-frequency writes: concurrent requests can clobber each other, and a partially written file can make get() blow up. Use the database store for anything multi-server, and never call set() inside a request loop or a queued job that could race.

Writes are not atomic across the application. There is no transaction wrapping read-modify-write. If you need "increment a counter" semantics, do it with a real column and a query, not with this package.

File permissions bite silently. With the JSON driver, the web server user needs write access to the file and its directory. A set() that appears to succeed but doesn't survive a reload is almost always a permissions or path problem — check storage_path() and the configured path first.

Type fidelity is entirely up to you. The JSON round-trip preserves int, bool, and null, but a value written as "false" (string) reads back as a truthy string. Normalise on write, and cast on read in your repository. The most common bug report for this package is a form checkbox saving as "1" and then (bool) behaving unexpectedly downstream.

Don't store secrets here. Values land in JSON on disk or plain columns in a table, with no encryption layer. API keys belong in .env / config / a secrets manager.

Beware of touching settings during application boot. Calling Setting::get() from a service provider's register() method runs before migrations in a fresh environment and will throw a "table not found" or "file not found" error during composer install or CI bootstrap. Read settings at the point of use, or wrap the access in a try/catch that falls back to config.

Config caching changes nothing but the config. php artisan config:cache is safe, but re-publish config/settings.php after a major upgrade — new store options have been added over the years, and a stale published file will silently miss them. The same applies to the migration: check the package's migrations/ directory after upgrading and publish anything new.

The facade is a singleton, mutated in place. Once you've called get() or set(), the store may hold state for the rest of the request. In long-running workers (Octane, queues) this can mean a stale snapshot. If you run under Octane, flush the instance between requests or read through your own cache layer instead.

Extension points. Every store implements the same driver contract, registered in the stores config map. If you need Vault, Redis, or a tenant-aware store, implement that contract and register a new driver rather than subclassing the JSON or database store — the two shipped drivers differ enough that subclassing gets awkward fast. The facade's store selection is driven by config, so swapping is a config change plus one class.

Version drift. The package was renamed from anlutro/l4-settings (Laravel 4 namespace) to anlutro/laravel-settings. Composer package name and PHP namespace are anlutro/l4-settings and Anlutro\LaravelSettings respectively — don't let the mismatch confuse you while debugging a class-not-found error in an older project.

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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin