Skip to content

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 HasConfigs trait 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:

bash
composer require obelaw/o-configs

Database Setup

Run the migrations to create the required tables:

bash
php artisan migrate

This creates two tables:

  • o_configs: Global application configurations
  • o_config_models: Model-specific configurations with polymorphic relationships

Database Schema

Global Configurations Table

php
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

php
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:

php
// 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:

php
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
<?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

php
$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:

php
// 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; // 1000

Configuration with Models in Different Contexts

php
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'); // true

Admin Interface with Filament

Setting Up Configuration Schemas

Define configuration schemas for the Filament admin interface:

php
// 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

php
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

php
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

php
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:

php
// 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:

php
OConfig::set('app.debug', true);
OConfig::set('features.enabled', ['analytics', 'social']);

get(string $path, $default = null): mixed

Get a configuration value with optional default:

php
$debug = OConfig::get('app.debug'); // Returns stored value or file config
$theme = OConfig::get('app.theme', 'light'); // Returns 'light' if not set

has(string $path): bool

Check if a configuration exists in the database:

php
if (OConfig::has('app.custom_feature')) {
    // Configuration exists in database
}

forget(string $path): bool

Remove a configuration from the database:

php
$removed = OConfig::forget('app.temporary_setting');

all(): array

Get all stored configurations:

php
$allConfigs = OConfig::all();

Model Configuration Service

configs(): ConfigurationModelService

Get the configuration service for a model:

php
$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:

php
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:

php
Text::make('field_name', 'config.path', 'Field Label', 'Placeholder text');

Configuration Patterns

Environment-Based Configurations

php
// 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

php
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

php
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

php
// 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

php
// 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:

php
// 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:

php
// 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:

php
// 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:

php
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

php
// 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

php
// 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

  1. Check if the configuration exists in the database:
php
if (oconfig()->has('app.name')) {
    echo "Config exists in database";
} else {
    echo "Falling back to file config";
}
  1. Verify the configuration path is correct:
php
// Check actual stored configurations
dd(oconfig()->all());

Model Configuration Not Working

  1. Ensure the trait is added to the model:
php
class User extends Model
{
    use HasConfigs; // Make sure this is present
}
  1. Check if migrations have run:
bash
php artisan migrate:status

Performance Issues

  1. Implement caching for frequently accessed configurations
  2. Use batch operations for bulk updates
  3. Consider indexing if you have many configurations

Migration Guide

From File-Based Configs

To migrate existing file-based configurations to OConfigs:

php
// 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.

Released under the MIT License.