modera/backend-config-utils-bundle
A Symfony bundle from Modera that provides utility services and helpers for backend configuration management, simplifying common configuration tasks in admin backends.
Read the fine print first. modera/backend-config-utils-bundle is a Symfony 2 bundle, not a Laravel package. Its composer.json will constrain symfony/symfony to the 2.x line and PHP to ~5.5–7.0, last tagged 2016-06-10, with zero dependents. You cannot composer require it into a Laravel 9/10/11 app — the install will fail on framework and PHP constraints, and there are no security patches behind it. Treat this as reference material, not a dependency.
Where to look, in order:
DependencyInjection/ — the extension and, more importantly, any CompilerPass that collects config-section services by tag. This is the whole architectural idea of the bundle in ~40 lines.Controller/ and any Abstract* class — the base class you're meant to extend for your own section. This is the extension point.Resources/config/services.xml — shows how sections are tagged and what the registry service is called.Resources/views/ — Twig templates that render the section inside Backend/Tools/Settings.Resources/config/routing.yml — how the section is routed into the backend UI.First use case to reproduce in Laravel: a settings screen where any package can contribute a named section (a tab/panel) without editing the settings controller. That's the only genuinely reusable idea here, and it maps cleanly onto Laravel:
// app/Settings/ConfigSectionRegistry.php
class ConfigSectionRegistry
{
protected array $sections = [];
public function add(string $key, ConfigSection $section): void
{
$this->sections[$key] = $section;
}
public function all(): array { return $this->sections; }
}
Bind it as a singleton, tag sections in a service provider, and resolve the tags in a booted() callback. You can have the equivalent of the bundle's compiler pass running in a single Laravel provider.
Section-as-a-service, registered by tag. In Modera, a section is a service tagged into a registry so the Settings screen discovers it automatically. In Laravel:
// app/Providers/SettingsServiceProvider.php
public function register(): void
{
$this->app->singleton(ConfigSectionRegistry::class);
$this->app->tag([MailSettingsSection::class, BillingSettingsSection::class], 'config.section');
}
public function boot(): void
{
$this->app->booted(function () {
$registry = $this->app->make(ConfigSectionRegistry::class);
foreach ($this->app->tagged('config.section') as $section) {
$registry->add($section->key(), $section);
}
});
}
$this->app->tagged() is the direct analogue of Symfony's findTaggedServiceIds compiler pass — but it resolves lazily at boot rather than at compile time, which matters (see Gotchas).
Abstract base class for the section. The bundle's value is that you extend an abstract controller and get form rendering, validation, persistence and permissions for free. Mirror that:
abstract class ConfigSection
{
abstract public function key(): string;
abstract public function label(): string;
abstract public function fields(): array; // name => ['type' => ..., 'rules' => ...]
abstract public function rules(): array;
public function view(): string { return 'settings.sections.form'; }
public function load(): array { /* read from settings store */ }
public function save(array $data): void { /* validate + persist */ }
}
Then a single SettingsController iterates the registry and renders each section with @include($section->view()). This is exactly the bundle's "contribute your own configuration section" contract, expressed in Blade.
Separate mutable settings from config(). The bundle stores section values in the database (Modera's config entity), while Symfony's parameters.yml holds deploy-time parameters. Keep that distinction in Laravel: .env/config/*.php for infrastructure, a settings table for anything a user edits in the UI. Do not config(['mail.host' => $value]) at runtime to fake it — it won't survive a queue worker restart.
Gate the sections, not the screen. In Modera, section visibility is driven by the backend security layer per section. In Laravel, give each ConfigSection an authorize(User $user): bool and filter the registry per request, rather than wrapping the whole settings page in one gate.
Reuse the settings value object across layers. Whatever you build, keep one SettingsRepository::get($section, $key, $default) used by both the settings UI and your application code, so there's exactly one read path and one cache to invalidate.
If you are actually on a legacy Modera/Symfony app (the only context where this package runs): register the bundle in AppKernel, ensure it loads after ModeraBackendSettingsBundle-style bundles whose registry it feeds, import its routing under your backend firewall prefix, run assets:install --symlink for its web assets, and clear the container cache after tagging any new section. Do not reorder bundles casually — compiler passes that collect tags run in bundle registration order.
DependencyInjection/ is not.tagged() resolves at runtime. If you register a tagged section from inside a booted() callback in another provider, ordering will silently drop it. Register tags in register(), resolve them in booted().save() must bust the cache — and in Laravel also bust the per-worker cache if you use Octane. A stale SettingsRepository singleton outliving a request is a classic Octane footgun.sections.mail.*) or a plugin author's title key will collide with yours.php artisan tinker then app(ConfigSectionRegistry::class)->all() to confirm your tag wiring; php artisan route:list to confirm section routes; a dd() in the booted() callback is faster than guessing at provider order.spatie/laravel-settings plus a ~60-line registry gives you the same capability with maintained dependencies. Steal the registry + abstract section pattern; leave the rest.How can I help you explore Laravel packages today?