OConfigs Package
OConfigs is a powerful Laravel package that enables dynamic configuration management through database storage. It allows you to override Laravel configuration values at runtime, store configuration per model instance, and manage settings through a beautiful Filament admin interface.
Features Overview
🔧 Dynamic Configuration Management
- Runtime Config Override: Override any Laravel config value without touching files
- Database Storage: Persistent configuration storage with JSON support
- Fallback System: Graceful fallback to file-based configs when database values don't exist
- Type Safety: Support for all data types including arrays, objects, and primitives
🎯 Model-Level Configuration
- Per-Model Settings: Individual configuration for each model instance
- Trait-Based Integration: Simple
HasConfigstrait for instant functionality - Polymorphic Relations: Efficient database structure using polymorphic relationships
- Isolated Configurations: Each model maintains its own configuration namespace
🎨 Admin Interface
- Filament Integration: Beautiful admin interface for configuration management
- Schema Builder: Define configuration forms with custom fields and sections
- Visual Editor: User-friendly interface for non-technical users
- Section Organization: Group related configurations into collapsible sections
🚀 Developer Experience
- Intuitive API: Simple helper functions and facades for easy integration
- Multiple Access Methods: Helper functions, facades, and service classes
- JSON Storage: Native JSON casting for complex data structures
- Laravel Integration: Seamless integration with Laravel's configuration system
Installation
Install the OConfigs package via Composer:
composer require obelaw/o-configsDatabase Setup
Run the migrations to create the required tables:
php artisan migrateThis creates two tables:
o_configs: Global application configurationso_config_models: Model-specific configurations with polymorphic relationships
Database Schema
Global Configurations Table
Schema::create('o_configs', function (Blueprint $table) {
$table->string('path')->index()->unique(); // Configuration path (e.g., 'app.name')
$table->json('value'); // Configuration value (JSON)
});Model Configurations Table
Schema::create('o_config_models', function (Blueprint $table) {
$table->morphs('modelable'); // Polymorphic relationship
$table->string('path')->index(); // Configuration path
$table->json('value'); // Configuration value (JSON)
$table->unique(['modelable_id', 'modelable_type', 'path']);
});Basic Usage
Global Configuration Management
Using the Helper Function
The oconfig() helper function provides the most convenient way to work with configurations:
// Get a configuration value (with fallback to Laravel config)
$appName = oconfig('app.name'); // Returns current app name
// Set a configuration value
oconfig()->set('app.name', 'My Custom App');
// Get the updated value
$newName = oconfig('app.name'); // Returns 'My Custom App'
// Set multiple values at once
oconfig([
'app.name' => 'Obelaw Application',
'app.timezone' => 'America/New_York',
'mail.from.name' => 'Obelaw Team'
]);
// Get with default value
$theme = oconfig('app.theme', 'light');Using the Facade
For more explicit usage, use the OConfig facade:
use Obelaw\Configs\Facades\OConfig;
// Set configuration
OConfig::set('app.debug', true);
// Get configuration
$debugMode = OConfig::get('app.debug');
// Check if configuration exists
if (OConfig::has('app.custom_setting')) {
$setting = OConfig::get('app.custom_setting');
}
// Remove configuration
OConfig::forget('app.temporary_setting');
// Get all configurations
$allConfigs = OConfig::all();Model-Level Configuration
Adding Configuration Support to Models
Add the HasConfigs trait to any Eloquent model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Obelaw\Configs\Traits\HasConfigs;
class User extends Model
{
use HasConfigs;
protected $fillable = [
'name', 'email', 'password'
];
}Managing Model Configurations
$user = User::find(1);
// Set user-specific configurations
$user->configs()->set('theme', 'dark');
$user->configs()->set('language', 'es');
$user->configs()->set('notifications', [
'email' => true,
'push' => false,
'sms' => true
]);
// Get user configurations
$theme = $user->configs()->get('theme'); // 'dark'
$language = $user->configs()->get('language', 'en'); // 'es' or 'en' as default
// Check if configuration exists
if ($user->configs()->has('notifications')) {
$notifications = $user->configs()->get('notifications');
}
// Remove configuration
$user->configs()->forget('theme');
// Get all user configurations
$allUserConfigs = $user->configs()->all();Advanced Usage
Working with Complex Data Types
OConfigs supports storing complex data structures:
// Store array configurations
oconfig()->set('app.features', [
'analytics' => true,
'social_login' => ['google', 'facebook'],
'payment_methods' => [
'stripe' => ['public_key' => 'pk_test_...'],
'paypal' => ['client_id' => 'paypal_client_...']
]
]);
// Store object configurations
oconfig()->set('app.api_settings', (object) [
'version' => 'v2',
'rate_limit' => 1000,
'endpoints' => [
'users' => '/api/v2/users',
'orders' => '/api/v2/orders'
]
]);
// Retrieve and use complex configurations
$features = oconfig('app.features');
$hasAnalytics = $features['analytics']; // true
$apiSettings = oconfig('app.api_settings');
$rateLimit = $apiSettings->rate_limit; // 1000Configuration with Models in Different Contexts
class Organization extends Model
{
use HasConfigs;
}
class Project extends Model
{
use HasConfigs;
}
// Organization-level settings
$org = Organization::find(1);
$org->configs()->set('billing.plan', 'enterprise');
$org->configs()->set('features.advanced_analytics', true);
// Project-level settings within the organization
$project = Project::find(1);
$project->configs()->set('deployment.auto_deploy', true);
$project->configs()->set('notifications.slack_webhook', 'https://...');
// Each model maintains separate configuration namespaces
$orgPlan = $org->configs()->get('billing.plan'); // 'enterprise'
$projectDeploy = $project->configs()->get('deployment.auto_deploy'); // trueAdmin Interface with Filament
Setting Up Configuration Schemas
Define configuration schemas for the Filament admin interface:
// In a service provider or configuration file
use Obelaw\Configs\Schema\Section;
use Obelaw\Configs\Schema\Fields\Text;
// Define configuration sections
Section::make(
name: 'Application Settings',
description: 'Basic application configuration options',
schema: function () {
return [
Text::make('app_name', 'app.name', 'Application Name', 'Enter application name'),
Text::make('app_url', 'app.url', 'Application URL', 'Enter application URL'),
Text::make('app_timezone', 'app.timezone', 'Timezone', 'Enter timezone'),
];
}
);
Section::make(
name: 'Mail Configuration',
description: 'Email and notification settings',
schema: function () {
return [
Text::make('mail_from_name', 'mail.from.name', 'From Name', 'Default sender name'),
Text::make('mail_from_address', 'mail.from.address', 'From Email', 'Default sender email'),
];
}
);
Section::make(
name: 'Feature Flags',
description: 'Enable or disable application features',
schema: function () {
return [
Text::make('analytics_enabled', 'features.analytics', 'Enable Analytics', 'true/false'),
Text::make('social_login', 'features.social_login', 'Social Login', 'true/false'),
];
}
);Accessing the Configuration Panel
The configuration panel is automatically available in your Filament admin interface. Users can:
- View all configuration sections
- Edit configuration values through forms
- Save changes that immediately take effect
- See current values with fallbacks to file configs
Integration Examples
Multi-Tenant Application
class Tenant extends Model
{
use HasConfigs;
}
class TenantService
{
public function setupTenantConfigs(Tenant $tenant, array $configs)
{
// Set tenant-specific configurations
$tenant->configs()->set('branding.logo', $configs['logo_url']);
$tenant->configs()->set('branding.colors', [
'primary' => $configs['primary_color'],
'secondary' => $configs['secondary_color']
]);
$tenant->configs()->set('features.enabled', $configs['features']);
$tenant->configs()->set('limits.users', $configs['user_limit']);
$tenant->configs()->set('billing.plan', $configs['plan']);
}
public function getTenantTheme(Tenant $tenant)
{
return [
'logo' => $tenant->configs()->get('branding.logo'),
'colors' => $tenant->configs()->get('branding.colors', [
'primary' => '#3490dc',
'secondary' => '#6cb2eb'
]),
'features' => $tenant->configs()->get('features.enabled', [])
];
}
}E-commerce Store Settings
class Store extends Model
{
use HasConfigs;
}
class StoreConfigurationService
{
public function configureStore(Store $store, array $settings)
{
// Store branding
$store->configs()->set('branding', [
'name' => $settings['store_name'],
'logo' => $settings['logo_url'],
'favicon' => $settings['favicon_url']
]);
// Payment settings
$store->configs()->set('payments', [
'stripe_enabled' => $settings['stripe_enabled'],
'paypal_enabled' => $settings['paypal_enabled'],
'currency' => $settings['currency'],
'tax_rate' => $settings['tax_rate']
]);
// Shipping configuration
$store->configs()->set('shipping', [
'free_shipping_threshold' => $settings['free_shipping_amount'],
'shipping_zones' => $settings['shipping_zones'],
'default_shipping_cost' => $settings['default_shipping']
]);
// Inventory settings
$store->configs()->set('inventory', [
'track_quantity' => $settings['track_inventory'],
'low_stock_threshold' => $settings['low_stock_alert'],
'backorders_allowed' => $settings['allow_backorders']
]);
}
public function getStoreCheckout(Store $store)
{
$paymentConfig = $store->configs()->get('payments', []);
$shippingConfig = $store->configs()->get('shipping', []);
return [
'payment_methods' => array_filter([
'stripe' => $paymentConfig['stripe_enabled'] ?? false,
'paypal' => $paymentConfig['paypal_enabled'] ?? false,
]),
'currency' => $paymentConfig['currency'] ?? 'USD',
'free_shipping_threshold' => $shippingConfig['free_shipping_threshold'] ?? 0,
];
}
}User Preference Management
class User extends Model
{
use HasConfigs;
}
class UserPreferenceService
{
public function setUserPreferences(User $user, array $preferences)
{
// UI preferences
$user->configs()->set('ui', [
'theme' => $preferences['theme'] ?? 'light',
'language' => $preferences['language'] ?? 'en',
'timezone' => $preferences['timezone'] ?? 'UTC',
'sidebar_collapsed' => $preferences['sidebar_collapsed'] ?? false
]);
// Notification preferences
$user->configs()->set('notifications', [
'email' => [
'news' => $preferences['email_news'] ?? true,
'updates' => $preferences['email_updates'] ?? true,
'marketing' => $preferences['email_marketing'] ?? false
],
'push' => [
'mentions' => $preferences['push_mentions'] ?? true,
'messages' => $preferences['push_messages'] ?? true
]
]);
// Privacy settings
$user->configs()->set('privacy', [
'profile_visible' => $preferences['public_profile'] ?? true,
'activity_visible' => $preferences['show_activity'] ?? false,
'analytics_consent' => $preferences['analytics_consent'] ?? false
]);
}
public function getUserDashboard(User $user)
{
$ui = $user->configs()->get('ui', []);
return [
'theme' => $ui['theme'] ?? 'light',
'language' => $ui['language'] ?? 'en',
'sidebar_collapsed' => $ui['sidebar_collapsed'] ?? false,
];
}
public function shouldSendNotification(User $user, string $type, string $channel)
{
$notifications = $user->configs()->get('notifications', []);
return $notifications[$channel][$type] ?? false;
}
}API Reference
Global Configuration Service
oconfig($key = null, $default = null)
Helper function for configuration management:
// Get service instance
$configService = oconfig();
// Get single value
$value = oconfig('app.name', 'Default App');
// Set multiple values
oconfig([
'app.name' => 'New Name',
'app.version' => '2.0'
]);OConfig Facade Methods
set(string $path, $value): mixed
Set a configuration value:
OConfig::set('app.debug', true);
OConfig::set('features.enabled', ['analytics', 'social']);get(string $path, $default = null): mixed
Get a configuration value with optional default:
$debug = OConfig::get('app.debug'); // Returns stored value or file config
$theme = OConfig::get('app.theme', 'light'); // Returns 'light' if not sethas(string $path): bool
Check if a configuration exists in the database:
if (OConfig::has('app.custom_feature')) {
// Configuration exists in database
}forget(string $path): bool
Remove a configuration from the database:
$removed = OConfig::forget('app.temporary_setting');all(): array
Get all stored configurations:
$allConfigs = OConfig::all();Model Configuration Service
configs(): ConfigurationModelService
Get the configuration service for a model:
$configService = $user->configs();The model configuration service provides the same methods as the global service but scoped to the specific model instance.
Schema Builder
Section::make(string $name, Closure $schema, string $description = null)
Create a configuration section for the admin interface:
Section::make(
name: 'API Settings',
description: 'Configure API access and limits',
schema: function () {
return [
Text::make('api_key', 'api.key', 'API Key', 'Enter API key'),
Text::make('rate_limit', 'api.rate_limit', 'Rate Limit', 'Requests per minute'),
];
}
);Text::make(string $name, string $path, string $label, string $placeholder)
Create a text input field:
Text::make('field_name', 'config.path', 'Field Label', 'Placeholder text');Configuration Patterns
Environment-Based Configurations
// Set configurations based on environment
if (app()->environment('production')) {
oconfig([
'app.debug' => false,
'logging.level' => 'error',
'cache.ttl' => 3600
]);
} else {
oconfig([
'app.debug' => true,
'logging.level' => 'debug',
'cache.ttl' => 60
]);
}Feature Flag Management
class FeatureFlagService
{
public function enableFeature(string $feature, bool $enabled = true)
{
oconfig()->set("features.{$feature}", $enabled);
}
public function isFeatureEnabled(string $feature): bool
{
return oconfig("features.{$feature}", false);
}
public function enableForUser(User $user, string $feature)
{
$user->configs()->set("features.{$feature}", true);
}
public function isEnabledForUser(User $user, string $feature): bool
{
// Check user-specific override first, then global setting
if ($user->configs()->has("features.{$feature}")) {
return $user->configs()->get("features.{$feature}");
}
return $this->isFeatureEnabled($feature);
}
}
// Usage
$featureFlags = new FeatureFlagService();
$featureFlags->enableFeature('new_dashboard', true);
$featureFlags->enableForUser($user, 'beta_features');
if ($featureFlags->isEnabledForUser($user, 'new_dashboard')) {
// Show new dashboard
}Configuration Inheritance
class ConfigurationManager
{
public function getInheritedConfig(Model $model, string $path, $default = null)
{
// 1. Check model-specific config
if ($model->configs()->has($path)) {
return $model->configs()->get($path);
}
// 2. Check parent model config (if applicable)
if (method_exists($model, 'parent') && $model->parent) {
if ($model->parent->configs()->has($path)) {
return $model->parent->configs()->get($path);
}
}
// 3. Check global config
if (oconfig()->has($path)) {
return oconfig($path);
}
// 4. Fallback to Laravel config or default
return config($path, $default);
}
public function setWithInheritance(Model $model, string $path, $value, bool $override = false)
{
// Only set if no inherited value exists or override is true
if (!$this->hasInheritedConfig($model, $path) || $override) {
$model->configs()->set($path, $value);
}
}
private function hasInheritedConfig(Model $model, string $path): bool
{
return $model->configs()->has($path) ||
(method_exists($model, 'parent') && $model->parent && $model->parent->configs()->has($path)) ||
oconfig()->has($path);
}
}Performance Considerations
Caching Strategies
// Cache frequently accessed configurations
$cachedConfig = Cache::remember("config.{$path}", 3600, function () use ($path) {
return oconfig($path);
});
// Cache user configurations
$userConfig = Cache::remember("user.{$user->id}.config", 1800, function () use ($user) {
return $user->configs()->all();
});
// Invalidate cache when configurations change
class ConfigCacheService
{
public function set(string $path, $value)
{
oconfig()->set($path, $value);
Cache::forget("config.{$path}");
}
public function setForUser(User $user, string $path, $value)
{
$user->configs()->set($path, $value);
Cache::forget("user.{$user->id}.config");
}
}Batch Operations
// Efficient batch updates
DB::transaction(function () use ($configurations) {
foreach ($configurations as $path => $value) {
oconfig()->set($path, $value);
}
});
// Bulk model configuration updates
$users = User::whereIn('id', $userIds)->get();
DB::transaction(function () use ($users, $config) {
foreach ($users as $user) {
foreach ($config as $path => $value) {
$user->configs()->set($path, $value);
}
}
});Best Practices
1. Configuration Naming
Use consistent, hierarchical naming conventions:
// Good
oconfig('app.features.analytics');
oconfig('mail.providers.smtp.host');
oconfig('payment.gateways.stripe.public_key');
// Avoid
oconfig('analytics_enabled');
oconfig('smtp_host');
oconfig('stripe_key');2. Default Values
Always provide sensible defaults:
// Always specify defaults for optional configurations
$theme = oconfig('ui.theme', 'light');
$pageSize = oconfig('pagination.page_size', 25);3. Type Consistency
Maintain consistent data types for the same configuration across different contexts:
// Good - always boolean
$user->configs()->set('notifications.email', true);
$organization->configs()->set('notifications.email', false);
// Avoid - mixed types
$user->configs()->set('notifications.email', 1);
$organization->configs()->set('notifications.email', 'yes');4. Validation
Validate configuration values before setting:
class ConfigValidator
{
public function setWithValidation(string $path, $value)
{
$this->validate($path, $value);
oconfig()->set($path, $value);
}
private function validate(string $path, $value)
{
switch ($path) {
case 'app.timezone':
if (!in_array($value, timezone_identifiers_list())) {
throw new InvalidArgumentException('Invalid timezone');
}
break;
case 'mail.from.address':
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('Invalid email address');
}
break;
}
}
}Security Considerations
Sensitive Configuration Protection
// Define sensitive configurations that should be encrypted
class SecureConfigService
{
private $sensitiveKeys = [
'api.secret_key',
'payment.stripe.secret_key',
'mail.password',
'database.password'
];
public function set(string $path, $value)
{
if (in_array($path, $this->sensitiveKeys)) {
$value = encrypt($value);
}
oconfig()->set($path, $value);
}
public function get(string $path, $default = null)
{
$value = oconfig($path, $default);
if (in_array($path, $this->sensitiveKeys) && $value !== $default) {
return decrypt($value);
}
return $value;
}
}Access Control
// Implement role-based configuration access
class ConfigAccessControl
{
public function canModify(User $user, string $path): bool
{
// Super admins can modify anything
if ($user->hasRole('super-admin')) {
return true;
}
// Regular admins can modify non-sensitive configs
if ($user->hasRole('admin')) {
return !$this->isSensitiveConfig($path);
}
// Users can only modify their own configs
return false;
}
private function isSensitiveConfig(string $path): bool
{
$sensitivePatterns = [
'api.secret',
'database.',
'mail.password',
'payment.',
];
foreach ($sensitivePatterns as $pattern) {
if (str_starts_with($path, $pattern)) {
return true;
}
}
return false;
}
}Troubleshooting
Common Issues
Configuration Not Taking Effect
- Check if the configuration exists in the database:
if (oconfig()->has('app.name')) {
echo "Config exists in database";
} else {
echo "Falling back to file config";
}- Verify the configuration path is correct:
// Check actual stored configurations
dd(oconfig()->all());Model Configuration Not Working
- Ensure the trait is added to the model:
class User extends Model
{
use HasConfigs; // Make sure this is present
}- Check if migrations have run:
php artisan migrate:statusPerformance Issues
- Implement caching for frequently accessed configurations
- Use batch operations for bulk updates
- Consider indexing if you have many configurations
Migration Guide
From File-Based Configs
To migrate existing file-based configurations to OConfigs:
// Migration script
$fileConfigs = [
'app.name' => config('app.name'),
'app.timezone' => config('app.timezone'),
'mail.from.name' => config('mail.from.name'),
// ... other configs
];
foreach ($fileConfigs as $path => $value) {
if ($value !== null) {
oconfig()->set($path, $value);
}
}Contributing
We welcome contributions to the OConfigs package! Please see our contributing guidelines for details.
License
The OConfigs package is open-source software licensed under the MIT license.
Related Packages
- Attrify - Dynamic attributes for models
- Trail - Audit logging and activity tracking
- Twist Framework - Modular application architecture