modera/config-bundle
A configuration management bundle for Modera-based PHP applications, providing centralized storage and retrieval of application settings. Integrates with the framework to help organize, access, and maintain config values across your project.
Frame this correctly before you clone anything. modera/config-bundle is a Symfony 2/3-era bundle, last released July 2016, with one star, zero dependents, and marked [READ-ONLY] (a subtree split of a larger Modera monorepo). For a Laravel developer, there is no "day-to-day" here in the sense of composer require and inject — the package does not fit Laravel's service-provider, config-repository, or cache abstractions, and installing it would mean owning an abandoned Symfony dependency forever. The realistic day-to-day use is as prior art: read its source to learn the pattern (DB-backed, typed, defaulted, cache-resolved config) and reimplement it natively in Laravel in a day or two. If you are on a legacy Symfony app, treat the steps below as a vendored/forked integration, not a package dependency.
Minimal steps (Symfony path, if you must run it)
composer require modera/config-bundle — expect to pin a tag or dev-master and resolve conflicts against modern symfony/* and doctrine/orm.app/AppKernel.php. There is no Symfony Flex recipe, so nothing auto-registers.doctrine:schema:update --force in dev, hand-written migration in production.Where to look first, in this order
Resources/config/services.xml (or DependencyInjection/) — the authoritative API surface, since documentation is effectively nonexistent. Note the service IDs and the interface each implements.Manager/ConfigManager.php — the read/write façade you inject into your services. Trace getConfigValue/setConfigValue-style methods and their fallback semantics.Entity/Configuration.php + Resources/config/doctrine/ — the persisted shape: almost certainly a flat name/value string pair, possibly with timestamps or a scope column.ConfigurationHandlerInterface (in a Config/ or Handler/ directory) — the extension point. Each config property is a handler object owning its default, validation, and form metadata.DependencyInjection/Compiler/ pass — shows which tags are collected and how handlers register.First use case. Global, operator-editable "site settings" that should change without a deploy: site.name, site.support_email, listing.items_per_page, feature.new_checkout_enabled. Anything an admin should tune without engineering, that is not environment-specific infrastructure config. In Laravel terms: this is the spatie/laravel-settings niche — typed settings with defaults, cached, editable at runtime.
Laravel-native first (the actual recommendation). Reimplement the pattern rather than adopting the bundle: a settings table (typed columns or a validated JSON schema, plus a key, value, type, group, updated_by, updated_at), a SettingsRepository contract bound in a service provider, a cached resolver with an explicit fallback chain, and an admin surface. This is 1–3 days of work and you own the security, caching, and audit story — none of which the 2016 bundle provides.
If porting the bundle's model into Laravel, mirror its handler-per-property design. Do not build a free-form key/value table with stringly-typed values; that is the design most teams regret by month six. Each setting should declare: a key, a type (string|int|bool|array|enum), a default, a validator, and a group/label for the admin form. This is exactly what the bundle's handler objects encode, and it is the part worth copying.
Wrap the vendor API in one application service. If you are integrating the bundle in Symfony (or forking), create a single Settings class depending on ConfigManager and exposing typed getters. Everything else depends on that, making replacement/removal a one-file change.
class Settings
{
public function __construct(private ConfigManager $config) {}
public function getSiteName(): string
{
return (string) $this->config->getConfigValue('site.name', 'Default Site');
}
public function getItemsPerPage(): int
{
return (int) $this->config->getConfigValue('listing.items_per_page', 25);
}
}
Three-tier fallback chain. Always resolve DB value → container/bundle parameter (so deploys can override) → hardcoded literal. A fresh database with zero config rows must still boot. Never let a missing row throw.
Cache aggressively, invalidate explicitly. Config reads are hot and writes are cold, so cache the fully resolved map (local/opcache plus Redis) and bust it via an event/observer on save. In Laravel: a SettingsSaved event plus a cache key version or tag. This is the single most important operational decision — the bundle's era predates the caching correctness tooling you now take for granted.
Typed columns over JSON blobs for anything you filter, sort, or validate on. Use text/json with cast/encoding for arrays and objects, but store primitives in native column types so the DB enforces integrity.
Encrypt secrets at rest. The bundle has no encryption story; integration credentials, API keys, and anything sensitive must be encrypted-cast or otherwise protected, and excluded from logs and config dumps. Audit every write: who, when, old value, new value.
Multi-tenant scoping via an indexed scope/tenant_id column, with a resolver that resolves the current tenant before cache lookup. Do not rely on a global settings table you filter in PHP.
symfony/* or doctrine/orm. Pin explicitly or fork. Run a compatibility matrix against your target PHP (8.2/8.3) before assuming it boots.[READ-ONLY] means issues/PRs are handled in the parent monorepo — you almost certainly cannot get fixes upstream. Anything you rely on, vendor and own, or (better) don't rely on it.filter_var, Validator::make, enums, casts. Do not trust raw DB strings at consumption points.getConfigValue so a migration gap doesn't throw.How can I help you explore Laravel packages today?