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

Settings Bundle Laravel Package

sylius/settings-bundle

Sylius Settings Bundle adds a database-backed settings system to Sylius apps, letting admins edit runtime configuration in the admin panel and developers read values via a simple service or Twig helpers. Grouped settings are persisted, so store configuration changes without code deployments.

View on GitHub
Deep Wiki
Context7

Getting Started

The honest first step: do not composer require this. sylius/settings-bundle is a Symfony2 bundle, not a framework-agnostic library. It registers via AppKernel, wires services through Symfony's DI container, persists through Doctrine ORM entity mappings, and builds its UI with Twig + Symfony Form types. None of those seams exist in Laravel. Symfony 2 reached end-of-life in 2018 and the repository is archived, so there are no PHP 8.x fixes and no security patches. For a Laravel codebase, the correct posture is reference architecture, not dependency.

Where to look first. If you clone the repo, read it in this order rather than starting from the bundle root:

  1. SettingsManager — the public API surface. This tells you the exact read/write contract you need to reproduce (get/set/has, defaults, per-scope resolution).
  2. The settings schema/registry classes — this is the typed-parameter layer: how a setting is declared (name, type, default, group/namespace) and how defaults are resolved when no row exists. This is the most valuable thing to port.
  3. The Doctrine entity + repository — the storage shape: a settings table keyed by name + scope, with a serialized value column. Extract the columns, not the code.
  4. The cache layer — where read-through and invalidation live. This is where most of the observable semantics sit, and the easiest part to get subtly wrong.

Minimal first use case. A small admin-configurable settings surface — branding colours, feature toggles, notification copy, per-tenant overrides — read at runtime and editable without a deploy. Scope it to a few dozen keys with a single-level scope hierarchy for the first iteration.

Fastest viable path for a Laravel team. Evaluate spatie/laravel-settings before writing anything. It gives you typed settings classes, migrations, per-model scoping, and cache integration, and it is actively maintained. Only build bespoke if it fails a specific acceptance test (e.g. a scope-resolution order or value-coercion rule you need to match byte-for-byte). In that case, use the Sylius bundle as a written spec:

// The shape you are reproducing, in Laravel idiom
Schema::create('settings', function (Blueprint $table) {
    $table->id();
    $table->string('scope')->default('global');
    $table->string('key');
    $table->string('type');          // string|int|bool|float|array
    $table->text('value')->nullable();
    $table->timestamps();
    $table->unique(['scope', 'key']);
});

Read the source for the domain (typed values, scope hierarchy, resolution order, cache strategy). Do not open a spike to "make the bundle run" — that spike has no exit.

Implementation Patterns

Typed accessor layer over a dumb table. The bundle's core idea is that the stored value is a string and the type is metadata; coercion happens on read and validation on write. Reproduce that explicitly rather than relying on Eloquent casts alone, because you need the type available before you can cast:

final class SettingsRepository
{
    public function get(string $key, string $scope = 'global', mixed $default = null): mixed
    {
        $row = $this->cached($scope, $key);
        if (! $row) {
            return $default; // decide throw-vs-default once, globally
        }
        return $this->cast($row->value, $row->type);
    }
}

Cache-aside repository, centralized. Read-through from cache, warm on boot or on-demand, invalidate on write. Use Redis (or another shared store) — a per-node file cache will serve divergent config across app servers after a write, which is a miserable failure mode to diagnose:

protected function cached(string $scope, string $key): ?Setting
{
    return Cache::rememberForever("settings:{$scope}:{$key}", fn () =>
        Setting::where('scope', $scope)->where('key', $key)->first()
    );
}

public function set(string $key, mixed $value, string $type, string $scope = 'global'): void
{
    Setting::updateOrCreate(
        ['scope' => $scope, 'key' => $key],
        ['value' => $this->serialize($value, $type), 'type' => $type]
    );
    Cache::forget("settings:{$scope}:{$key}");
}

Explicit scope resolution order. The bundle's reusable asset is the hierarchy pattern (global → tenant → entity override). Formalize the order and put it in one method so it can be tested and documented:

public function resolve(string $key, array $context, mixed $default = null): mixed
{
    foreach (['entity:'.$context['entity_id'] ?? null, 'tenant:'.$context['tenant_id'] ?? null, 'global'] as $scope) {
        if ($scope && ($v = $this->get($key, $scope, $sentinel)) !== $sentinel) {
            return $v;
        }
    }
    return $default;
}

Service façade for the hot path. Most reads should go through a single Settings service injected where needed, with a settings('brand.color', '#000') helper for views/Blade. Keep reads off the DB entirely once warm.

Admin integration. Rebuild the editing screen in Filament/Nova/Blade. Do not attempt to port the Twig + Symfony Forms UI — it is net-new work regardless. Gate it behind permissions and log writes; the bundle does not give you an audit trail, and you will want one the first time someone "fixes" a setting in production.

Feature-flag mapping. Settings with a bool type double as feature toggles. If you expect high change frequency or per-user rollout rules, use a dedicated flag system instead — settings are low-write by design, and forcing flag semantics onto them corrupts both models.

Migration pattern for an existing Sylius source. If a legacy instance exists, export directly from the Doctrine table (name, scope/namespace, value, type, timestamps) to JSON before building any UI, then run a differential check: for a sample of keys, assert the Laravel implementation returns the same typed value as the source.

Gotchas and Tips

The install trap. Composer will not resolve this against a modern Laravel app, and if it somehow did, you would be shipping an unmaintained Doctrine-coupled Symfony2 bundle into an Eloquent application. Flag the Symfony2-vs-Laravel mismatch in the ticket so nobody re-estimates this as "just add the package."

Type coercion drift is the #1 bug source. Untyped storage plus ad-hoc casting produces classic failures: "false" is truthy, "0" and "" behave differently in PHP 7 vs 8 comparisons, and array serialization formats drift between versions. Write the cast rules down, unit-test them, and centralize coercion in exactly one method.

Pick a missing-key policy and never deviate. Throw or return a default? Mixing the two produces production incidents where a typo in a key silently disables a feature. Default-return for display config; throw for anything that affects money, permissions, or security.

Cache stampede on cold cache. After a deploy, a flush, or a new node joining, every request can hit the settings table at once. Warm on boot or guard with a short lock. Settings are read-heavy and change rarely — design for zero read latency when warm.

Invalidation must be centralized. If you run more than one app server, this is non-negotiable. Local file cache = nodes serving different configuration on the same request pattern. Use a shared cache store and make write-through invalidation the only write path.

Rollback divergence. Rolling back an app version can leave stored settings that no longer match the deployed casts or schema. Treat settings as versioned data: additive-only type changes, and never repurpose an existing key with a different type.

Archived means total ownership. No security patches, no PHP compatibility fixes, no issue triage. If you fork instead of reimplementing, you own the fork indefinitely — including every transitive Symfony 2 advisory — with no upstream to file against. For a commodity capability, that is a bad trade.

Zero dependents and 21 stars is a support signal, not a popularity contest. There is no community to validate edge cases or share migration pain. Internal documentation will be your only source of truth, and it will be written by whoever does the port — a bus-factor risk.

No audit trail out of the box. Who changed which setting, when, and rollback are not provided. Add a settings_audit table (or spatie/laravel-activitylog) if the settings affect money, compliance, or customer-facing behaviour.

Don't fork unless a live Sylius deployment imposes it. If there is no existing Sylius data to preserve, the port is a small, well-understood build (days, not weeks) that you can complete against current PHP and Laravel — exactly the target this bundle cannot reach. The bundle's job is to be a requirements checklist, not a dependency.

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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle