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.
Installation:
composer require chillerlan/php-settings-container
Requires PHP 8.4+ (or PHP 8.1+ for older versions).
Basic Container:
use Chillerlan\SettingsContainer\SettingsContainerAbstract;
class AppSettings extends SettingsContainerAbstract {
protected string $app_name;
protected int $max_retries = 3;
}
First Use Case:
$settings = new AppSettings(['app_name' => 'MyApp']);
echo $settings->app_name; // "MyApp"
$settings->max_retries = 5; // Override default
SettingsContainerAbstracttoArray(), fromJSON(), toJSON()Use the container to create immutable configuration objects that can be passed around safely:
class DatabaseConfig extends SettingsContainerAbstract {
protected string $host;
protected int $port;
protected string $username;
protected string $password;
public function __construct(array $config) {
parent::__construct($config);
$this->password = password_hash($this->password, PASSWORD_DEFAULT);
}
}
Combine traits from different libraries into a single container:
use LibraryOne\OptionsTrait;
use LibraryTwo\MoreOptionsTrait;
class CombinedSettings extends SettingsContainerAbstract {
use OptionsTrait, MoreOptionsTrait;
protected string $custom_field;
}
Leverage built-in JSON support for configuration files:
// Load from JSON
$settings = new AppSettings();
$settings->fromJSON(file_get_contents('config.json'));
// Save to JSON
file_put_contents('config.json', $settings->toJSON());
Use magic methods for runtime validation:
class UserSettings extends SettingsContainerAbstract {
protected string $email;
protected function set_email(string $value): void {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email");
}
$this->email = $value;
}
}
Use the container for Laravel config (e.g., in AppServiceProvider):
public function boot(): void {
$this->app->singleton('settings', function () {
return new AppSettings(config('app.settings'));
});
}
Load settings from .env:
$settings = new AppSettings($_ENV);
Bind the container to Laravel's DI container:
$this->app->bind('settings', function () {
return new AppSettings(config('settings'));
});
Property Hooks Conflict (PHP 8.4+):
set_foo() and get_foo() if the property has hooks.Non-Existent Properties:
null.[ThrowOnInvalidProperty(true)] to throw exceptions:
#[ThrowOnInvalidProperty(true)]
class StrictSettings extends SettingsContainerAbstract { ... }
Trait Initialization Order:
construct() to enforce logic:
protected function OptionsTrait(): void {
$this->foo = strtoupper($this->foo);
}
Serialization Caveats:
serialize()/unserialize() bypass magic getters/setters. Use toArray()/fromIterable() for consistent behavior.Public Properties:
Check Property Access:
Use var_dump($container->toArray()) to inspect all properties.
Enable Strict Mode:
Temporarily enable [ThrowOnInvalidProperty(true)] to catch undefined property access.
Trait Debugging:
Override construct() to log trait initialization:
protected function construct(): void {
error_log('Initializing traits...');
parent::construct();
}
Avoid Magic Methods in Loops: Cache frequently accessed properties:
private ?string $cachedFoo = null;
public function getFoo(): string {
return $this->cachedFoo ?? ($this->cachedFoo = $this->foo);
}
Use toArray() for Bulk Access:
Fetch all properties at once to minimize magic method calls:
$allSettings = $container->toArray();
Custom JSON Handling:
Override toJSON()/fromJSON() for custom serialization:
public function toJSON(int $options = 0): string {
return json_encode($this->toArray(), $options | JSON_PRETTY_PRINT);
}
Add Validation Traits: Create reusable validation traits:
trait ValidatedEmail {
protected function set_email(string $value): void {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new \InvalidArgumentException("Invalid email");
}
$this->email = $value;
}
}
Merge Configurations:
Implement fromIterable() to merge multiple sources:
public function fromIterable(iterable $properties): static {
$current = $this->toArray();
foreach ($properties as $key => $value) {
$current[$key] = $value;
}
return new static($current);
}
Environment-Aware Defaults:
Use construct() to set defaults based on environment:
protected function construct(): void {
if (app()->environment('local')) {
$this->debug = true;
}
}
Cache Settings: Cache the container instance in Laravel:
$settings = Cache::remember('app.settings', now()->addHours(1), function () {
return new AppSettings(config('app.settings'));
});
Config Binding: Bind the container to Laravel's config:
$this->app->bind('settings', function () {
return new AppSettings(config('settings'));
});
Environment-Specific Containers: Create environment-specific containers:
class LocalSettings extends SettingsContainerAbstract { ... }
class ProductionSettings extends SettingsContainerAbstract { ... }
$settings = app()->environment('local')
? new LocalSettings(config('settings'))
: new ProductionSettings(config('settings'));
Service Provider Integration: Load settings in a service provider:
public function register(): void {
$this->app->singleton('settings', function () {
return new AppSettings(config('settings'));
});
}
Validation with Laravel Rules: Combine with Laravel's validation:
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($settings->toArray(), [
'email' => 'required|email',
'max_retries' => 'integer|min:1',
]);
Overusing Magic Methods: Avoid excessive magic getters/setters for simple properties. Use direct access when possible.
Ignoring Immutability: Treat the container as immutable after initialization. Avoid modifying properties directly unless using magic setters.
Mixing State and Logic: Keep business logic out of the container. Use traits or separate classes for complex logic.
Global State: Avoid using a single global container instance. Prefer dependency injection or scoped bindings.
How can I help you explore Laravel packages today?