helis/settings-manager-bundle
Symfony bundle for application settings management: define typed settings, persist values with Doctrine, validate and cache them, and read settings via services. Ideal for runtime-configurable preferences and admin-editable configuration without redeploying.
This is a Symfony bundle, not a Laravel package — there's no ServiceProvider, no config/settings.php, and no vendor:publish. Before you invest a day in it, decide which of these you actually are:
Minimal Symfony-side setup:
composer require helis/settings-manager-bundle:^4.2
// config/bundles.php
return [
// ...
Helis\SettingsManagerBundle\SettingsManagerBundle::class => ['all' => true],
];
Then create the package config file. Read DependencyInjection/Configuration.php in the vendor directory first — for a 30-star bundle with zero dependents, that file (plus the Tests/ folder) is the authoritative documentation, not the README. It tells you the exact config tree keys, defaults, and whether values are scalars, arrays, or typed objects.
First use case worth trying: operational knobs that ops/product want to tune without a code deploy — rate limits, pagination sizes, feature thresholds, external API timeouts. Start with one non-critical value, wire it into one service, ship it, and confirm the cache-clear story before doing anything broader.
Where to look, in order:
DependencyInjection/Configuration.php — the real schema.*SettingsManager*) — the runtime API you'll inject.Tests/ — actual behavior, including edge cases the docs forget.Resources/config/services.* — service IDs and aliases for autowiring.Inject the manager, not raw arrays. The whole value of a settings bundle disappears if you sprinkle $settings['x'] through controllers. Wrap it:
final class PricingPolicy
{
public function __construct(private SettingsManagerInterface $settings) {}
public function maxDiscountBps(): int
{
return (int) $this->settings->get('pricing.max_discount_bps');
}
}
Now business logic depends on PricingPolicy, which you can fake in tests, instead of on a framework service.
Define your own interface over it. Bind the bundle's manager to your App\Settings\SettingsRepository interface in services.yaml. This is your escape hatch when the bundle goes stale (which, with 0 dependents, you should assume it eventually will):
services:
App\Settings\SettingsRepository:
'@helis_settings_manager.settings_manager' # confirm the real service id via debug:container
Group by domain, not by type. pricing.*, shipping.*, notifications.*. When a value is read in exactly one place, inline it into that service's constructor binding rather than creating a global grab-bag.
Constrain at boot, not at read. If the bundle supports validation (Symfony Validator integration is common in these packages), assert types and ranges when the container is built. A settings typo that only explodes at 3am in a checkout flow is worse than a failed deploy.
Per-environment overrides via the standard Symfony mechanism. Define defaults in config/packages/settings_manager.yaml, then override in config/packages/prod/settings_manager.yaml or config/packages/test/. In tests, override explicitly per-test with a KernelTestCase and a custom config array — never rely on the test env defaults matching prod semantics.
Laravel translation of the same pattern. If this bundle inspired you, the Laravel equivalent is roughly:
// app/Support/Settings/Settings.php (bound as singleton)
final class Settings
{
public function __construct(private Repository $cache, private array $defaults) {}
public function get(string $key, mixed $fallback = null): mixed
{
return $this->cache->rememberForever("settings.$key",
fn () => DB::table('settings')->where('key', $key)->value('value') ?? $fallback
);
}
}
Keep config('...') for defaults and secrets (env-derived), and the DB/cache layer for runtime-tunable values. That's the split this bundle is trying to enforce in Symfony.
Integration tip: if the bundle exposes the settings as container parameters, prefer %param%-style constructor binding for values that are truly static per deploy, and the manager service for anything you'd want to change without a rebuild. Mixing the two indiscriminately is the most common source of "why didn't my change take effect."
Symfony bundles do not run in Laravel. No auto-discovery, no register(), no boot(). Any guide that says "just require it" assumes a Symfony kernel. This is the single biggest trap for a Laravel dev reading this package.
Check which Symfony version you actually resolve to. 4.2.0 added Symfony 7 and 8 support (^4.2 is the first line you can use on a modern Symfony). That means your Symfony app sets the real PHP floor — Symfony 7 needs PHP 8.2+, Symfony 8 needs 8.4+. Your Laravel app's PHP version is irrelevant here; the sibling service's is not. If that service is pinned to Symfony 6, you'll resolve an older bundle line and miss the DI fix below.
If you use a simple/static provider, you need 4.2.0. PR #110 fixes dependency injection on non-lazy simple providers. Small bundles with per-provider wiring are exactly where this class of bug hides, and the symptom (the manager resolving fine in a container-compiled test but not at runtime, or vice versa) is nasty to diagnose. Bump to ^4.2 and read the diff if you're on 4.1.x and use that provider type.
Config-defined settings are frozen at container compile time. If values live in YAML, changing them requires a cache warmup/clear. In production that means a deploy step (bin/console cache:clear), not a runtime toggle. If you need runtime edits, confirm the bundle has (or you add) a DB/cache-backed storage layer — otherwise the whole "without redeploying" premise is false.
Never put secrets in settings config. Settings tend to be surfaced by debug:container, debug:config, and sometimes dumped into logs or error pages. Use Symfony Secrets or env vars (%env(...)%) for credentials; keep this bundle for non-sensitive operational values.
Env vars arrive as strings. If your settings tree accepts ints/bools, check whether the bundle coerces or whether you need %env(int:FOO)% / %env(bool:FOO)%. Silent "false" truthiness bugs are a classic here.
Pin the version and read the changelog. Now maintained against current Symfony, which is a good sign, but with no dependents your usage path is still lightly exercised. Use composer require helis/settings-manager-bundle:^4.2 with a tight constraint, and vendor-review the diff before crossing a minor boundary.
Have a fork plan. With no dependents, you are effectively the only person exercising your usage path. Keep your integration behind your own interface so a fork-and-patch is a half-day, not a rewrite.
Debugging commands that actually help:
bin/console debug:config helis_settings_manager # effective merged config
bin/console debug:container --tag=... # find the manager service
bin/console lint:container # catch bad parameter bindings
Autowiring ambiguity. Small bundles often register a manager plus a decorator or a per-scope variant. If you get "multiple services implement X," disambiguate with an explicit alias or named binding rather than guessing.
Extension points to check for (worth 10 minutes of source reading, saves hours later): a custom settings loader/source, a validator hook, and whether the manager is decoratable. Decorating the manager service is usually the cleanest place to add caching, logging of reads, or audit trails for who changed what.
Don't leak settings into global template variables or a facade-style helper. It feels convenient for two weeks and then makes every value untraceable. One typed accessor per domain, injected where used.
How can I help you explore Laravel packages today?