chillerlan/php-settings-container
Lightweight settings container for PHP that decouples configuration logic from your application. Provides a SettingsContainerInterface with property-hook style access (for PHP < 8.4). Not a dependency injection container.
config() cache or environment variables). Avoids global state pollution by encapsulating settings in typed objects.config() for domain-specific settings (e.g., feature flags, API clients) where immutability is critical.config() system or replace it selectively.JsonSerializable, Serializable interfaces align with Laravel’s caching (e.g., Redis, file-based) and API response needs.set_*/get_* methods enable runtime validation (e.g., sanitizing API keys), similar to Laravel’s Form Requests but at the config layer.__get() in ArrayAccess traits). Mitigate by:
_get_foo()).#[ThrowOnInvalidProperty] to fail fast on invalid access.config() array. Workaround: Use a factory pattern to rebuild containers with new data.phpstan using the provided rules-magic-access.neon.config()?config() arrays into SettingsContainer instances?SettingsContainer for immutable settings, config() for mutable ones)?config()?SettingsContainer in unit tests (e.g., for dependency injection)?Testing facade?ThrowOnInvalidProperty be enabled globally or per-container?config(), env(), or third-party config managers (e.g., vlucas/phpdotenv).serialize()/unserialize() for persistent storage.@property-write for validation).use DatabaseSettingsTrait, use ApiClientSettingsTrait).class AppSettings extends SettingsContainerAbstract {
use LoggingSettingsTrait, AnalyticsSettingsTrait;
}
config() in a SettingsContainer for immutable access:
$settings = new SettingsContainer([
'app.name' => config('app.name'),
'database.connections' => config('database.connections'),
]);
fromJSON() to load from Laravel’s cached config files.config() calls with SettingsContainer instances in services.public function register() {
$this->app->singleton(AppSettings::class, fn() => new AppSettings(config('app.settings')));
}
$this->app->bind(AppSettings::class, fn() => new AppSettings($this->app['config']['app.settings']));
public function __construct(private AppSettings $settings) {}
.env via fromIterable() or a custom trait:
trait EnvSettingsTrait {
public function loadFromEnv(): static {
return $this->fromIterable($_ENV);
}
}
Validator via custom set_* methods:
protected function set_apiKey(string $value): void {
if (!Validator::make(['key' => $value], ['key' => 'required|string'])->passes()) {
throw new \InvalidArgumentException('Invalid API key');
}
$this->apiKey = $value;
}
composer require chillerlan/php-settings-container ^3.0
namespace App\Config;
use Chillerlan\SettingsContainer\SettingsContainerAbstract;
class AppSettings extends SettingsContainerAbstract {
protected string $appName;
protected array $database;
// ...
}
$this->app->singleton(AppSettings::class, fn() => new AppSettings(config('app.settings')));
config('app.name')$settings->appName (injected via constructor).trait DatabaseSettingsTrait {
protected string $defaultConnection;
protected array $connections;
protected function DatabaseSettingsTrait(): void {
$this->defaultConnection = $this->connections['mysql']['default'] ?? 'mysql';
}
}
rules-magic-access.neon).LoggingSettingsTrait) without touching core config.config()).#[ThrowOnInvalidProperty] to fail fast.@property).config() to SettingsContainer.trait ErrorHandlingTrait {
protected function handleInvalidProperty(string $property): void {
throw new \RuntimeException("Invalid setting: {$property}");
}
}
toArray()/fromIterable() vs. Laravel’s config() caching. Consider memoization for read-heavy workloads.serialize()/unserialize().| Failure Scenario | Impact | Mitigation |
|---|---|---|
| Invalid property access | Silent null or exception |
Enable #[ThrowOnInvalidProperty] globally. |
| Trait method |
How can I help you explore Laravel packages today?