Skip to content

Di migration Settings and ModSettings services#9177

Open
MissAllSunday wants to merge 7 commits intoSimpleMachines:release-3.0from
MissAllSunday:di_migration
Open

Di migration Settings and ModSettings services#9177
MissAllSunday wants to merge 7 commits intoSimpleMachines:release-3.0from
MissAllSunday:di_migration

Conversation

@MissAllSunday
Copy link
Copy Markdown
Contributor

This MR aims to split the configuration management into two separate, independent services fit also adds a few docs to keep track of already refactored services.

  1. SettingsService - Manages Settings.php (file-based configuration)
  2. ModSettingsService - Manages database settings (runtime configuration)

Key Features

Fully Independent: Both services can load and manage settings without depending on the Config class
Backward Compatible: Existing code using Config continues to work
Performance Optimized: Smart loading strategy reuses Config data when available
Testable: Can be tested in isolation without global state
Future-Proof: Clean migration path to eventually replace Config entirely

Architecture

┌─────────────────────────────────────────────────────────┐
│                   Application Code                       │
└──────────┬──────────────────────────┬───────────────────┘
           │                          │
           │ (Old Way)                │ (New Way)
           ▼                          ▼
┌──────────────────┐      ┌──────────────────────────────┐
│   Config Class   │      │   SettingsService            │
│   (Facade)       │      │   ModSettingsService         │
│                  │      │                              │
│ - $boardurl      │      │ - getBoardUrl()              │
│ - $modSettings   │      │ - get('setting')             │
└──────────────────┘      └────────┬─────────────────────┘
                                   │
                                   │ Registered in
                                   ▼
                       ┌──────────────────────┐
                       │   DI Container       │
                       │   (Singleton)        │
                       └──────────────────────┘

Why Split?

SettingsService (Settings.php)

  • Source: File-based (Settings.php)
  • Scope: System-level infrastructure
  • Mutability: Rarely changes
  • Dependencies: None (needed before DB connection)
  • Examples: Database credentials, directory paths, core settings
  • Caching: Not needed (file access is fast)

ModSettingsService (Database)

  • Source: Database (settings table)
  • Scope: Application-level features
  • Mutability: Frequently updated via admin panel
  • Dependencies: Requires database + cache
  • Examples: Forum features, mod settings, user preferences
  • Caching: Aggressive (DB queries are slow)

Usage Examples

Old Way (Still Works - Backward Compatible)

// Settings.php values
$boardUrl = Config::$boardurl;
$boardDir = Config::$boarddir;

// Database settings
$setting = Config::$modSettings['some_setting'] ?? 'default';
Config::updateModSettings(['key' => 'value']);
Config::reloadModSettings();

New Way - SettingsService

use SMF\Infrastructure\Container;
use SMF\Services\SettingsService;

// Get service from container
$settings = Container::get(SettingsService::class);

// Or use the Config facade
$settings = Config::getSettingsService();

// Get Settings.php values
$boardUrl = $settings->getBoardUrl();
$boardDir = $settings->getBoardDir();
$dbType = $settings->getDatabaseType();
$forumName = $settings->getForumName();

// Check maintenance mode
if ($settings->isMaintenanceMode()) {
    $level = $settings->getMaintenanceLevel();
}

// Update Settings.php
$settings->updateFile([
    'boardurl' => 'https://example.com',
    'maintenance' => 1,
]);

New Way - ModSettingsService

use SMF\Infrastructure\Container;
use SMF\Services\ModSettingsService;

// Get service from container
$modSettings = Container::get(ModSettingsService::class);

// Or use the Config facade
$modSettings = Config::getModSettingsService();

// Get settings
$value = $modSettings->get('some_setting', 'default');
$all = $modSettings->getAll();

// Check if setting exists
if ($modSettings->has('feature_enabled')) {
    // ...
}

// Get multiple settings
$settings = $modSettings->getMultiple(['setting1', 'setting2'], 'default');

// Update settings
$modSettings->update([
    'setting1' => 'value1',
    'setting2' => 'value2',
]);

// Delete settings
$modSettings->delete('old_setting');
$modSettings->delete(['old1', 'old2']);

// Increment/decrement numeric values
$modSettings->increment('counter');
$modSettings->decrement('counter', 5);

// Reload from database
$modSettings->reload();

// Clear cache
$modSettings->clearCache();

Dependency Injection (Best Practice)

use SMF\Services\Contracts\SettingsServiceInterface;
use SMF\Services\Contracts\ModSettingsServiceInterface;

class MyService
{
    public function __construct(
        private SettingsServiceInterface $settings,
        private ModSettingsServiceInterface $modSettings
    ) {}

    public function doWork(): void
    {
        // Use Settings.php values
        $boardDir = $this->settings->getBoardDir();

        // Use database settings
        $enabled = $this->modSettings->get('feature_enabled', false);
    }
}

// Register in ServicesList.php
return [
    MyService::class => [
        'arguments' => [
            SettingsService::class,
            ModSettingsService::class,
        ],
        'shared' => true,
    ],
];

API Reference

SettingsServiceInterface

Method Description Return
get(string $key, mixed $default = null) Get config value mixed
set(string $key, mixed $value) Set config value (memory only) void
updateFile(array $configVars, bool $keepQuotes = false, bool $rebuild = false) Update Settings.php bool
getBoardUrl() Get board URL string
getScriptUrl() Get script URL string
getBoardDir() Get board directory string
getSourcesDir() Get sources directory string
getCacheDir() Get cache directory string
getLanguagesDir() Get languages directory string
isMaintenanceMode() Check maintenance mode bool
getMaintenanceLevel() Get maintenance level int
getForumName() Get forum name string
getDatabaseType() Get database type string
getDatabaseServer() Get database server string
getDatabaseName() Get database name string
getDatabasePrefix() Get database prefix string

ModSettingsServiceInterface

Method Description Return
get(string $key, mixed $default = null) Get mod setting value mixed
getAll() Get all mod settings array
has(string $key) Check if setting exists bool
set(string $key, mixed $value) Set setting (memory only) void
update(array $settings, bool $update = false) Update settings in DB void
delete(string|array $keys) Delete setting(s) void
reload() Reload from database void
clearCache() Clear settings cache void
getMultiple(array $keys, mixed $default = null) Get multiple settings array
hasAny(array $keys) Check if any exist bool
hasAll(array $keys) Check if all exist bool
increment(string $key, int $amount = 1) Increment numeric value void
decrement(string $key, int $amount = 1) Decrement numeric value void

Migration Checklist

When migrating code to use the new services:

  • Identify Config usage in your code
  • Determine if it's Settings.php or database settings
  • For Settings.php values, use SettingsService
  • For database settings, use ModSettingsService
  • For new classes, inject interfaces via constructor
  • For existing code, use Config::getSettingsService() or Config::getModSettingsService()
  • Test thoroughly to ensure backward compatibility
  • Update tests to use service interfaces

Benefits

1. Single Responsibility

Each service has one clear purpose:

  • SettingsService: Manage Settings.php
  • ModSettingsService: Manage database settings

2. Better Dependencies

// Only need Settings.php values
class FileManager {
    public function __construct(SettingsServiceInterface $settings) {
        $this->boardDir = $settings->getBoardDir();
    }
}

// Only need database settings
class FeatureManager {
    public function __construct(ModSettingsServiceInterface $modSettings) {
        $this->enabled = $modSettings->get('feature_enabled');
    }
}

3. Improved Testing

// Mock only what you need
$mockModSettings = $this->createMock(ModSettingsServiceInterface::class);
$mockModSettings->method('get')->willReturn('test_value');

$service = new MyService($mockModSettings);

4. Performance Optimization

  • SettingsService: No caching needed (file is fast)
  • ModSettingsService: Aggressive caching (DB queries are slow)

5. Security Separation

  • SettingsService: Contains sensitive data (DB passwords)
  • ModSettingsService: User-configurable, less sensitive

Service Registration

Both services are registered in Sources/Infrastructure/ServicesList.php:

return [
    SettingsService::class => [
        'shared' => true,  // Singleton
    ],
    ModSettingsService::class => [
        'shared' => true,  // Singleton
    ],
];

Files Structure

Sources/
├── Services/
│   ├── Contracts/
│   │   ├── SettingsServiceInterface.php      # Settings.php interface
│   │   └── ModSettingsServiceInterface.php   # Database settings interface
│   ├── SettingsService.php                   # Settings.php implementation
│   └── ModSettingsService.php                # Database settings implementation
├── Infrastructure/
│   └── ServicesList.php                      # Service registration
└── Config.php                                # Facade to both services

Backward Compatibility

All existing code continues to work:

  • Config::$boardurl
  • Config::$modSettings['key']
  • Config::updateModSettings()
  • Config::reloadModSettings()
  • Config::updateSettingsFile()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant