Skip to main content

Overview

The Container is the heart of ForkBB’s architecture. It provides:
  • Dependency injection for loose coupling
  • Service registration with shared and multiple instances
  • Lazy loading for performance
  • Parameter substitution for configuration
  • Factory methods for complex initialization
The Container is based on the artoodetoo/container library.

Container Class

The Container class is located at app/Core/Container.php:
app/Core/Container.php
class Container
{
    protected array $instances = [];  // Instantiated services
    protected array $shared = [];     // Shared service definitions
    protected array $multiple = [];   // Multiple instance definitions
    
    public function __construct(array $config = [])
    {
        if (isset($config['shared'])) {
            $this->shared = $config['shared'];
        }
        
        if (isset($config['multiple'])) {
            $this->multiple = $config['multiple'];
        }
        
        $this->instances = $config;
    }
    
    // Get service or parameter
    public function __get(string $key): mixed
    
    // Set service or parameter
    public function __set(string $key, mixed $service): void
    
    // Check if service is initialized
    public function isInit(string $name): bool
}

Service Registration

Services are registered in configuration files:
app/config/main.dist.php
return [
    // Simple parameters
    'BASE_URL' => 'https://example.com',
    'DEBUG' => 0,
    'MAX_POST_SIZE' => 65536,
    
    // Shared services (singletons)
    'shared' => [
        // Strings become class names
        'Validator' => \ForkBB\Core\Validator::class,
        'Lang' => \ForkBB\Core\Lang::class,
        
        // Arrays provide constructor parameters
        'DB' => [
            'class'    => \ForkBB\Core\DB::class,
            'dsn'      => '%DB_DSN%',
            'username' => '%DB_USERNAME%',
            'password' => '%DB_PASSWORD%',
            'options'  => '%DB_OPTIONS%',
            'prefix'   => '%DB_PREFIX%',
        ],
        
        // Factory methods
        'user' => '@users:current',
        'config' => '@ConfigModel:init',
    ],
    
    // Multiple instance services (not cached)
    'multiple' => [
        'Topic' => \ForkBB\Models\Topic\Topic::class,
        'Post' => \ForkBB\Models\Post\Post::class,
    ],
];

Service Types

// Configuration values
'BASE_URL' => 'https://example.com',
'DEBUG' => 0,
'MAX_POST_SIZE' => 65536,

// Access
$url = $c->BASE_URL;
$debug = $c->DEBUG;

Parameter Substitution

The container supports parameter substitution using %param% syntax:
'BASE_URL' => 'https://example.com',
'DB_DSN' => 'mysql:host=localhost;dbname=forum',
'DB_PREFIX' => 'forum_',

'shared' => [
    'DB' => [
        'class' => \ForkBB\Core\DB::class,
        'dsn' => '%DB_DSN%',          // Substituted with 'mysql:host=...'
        'prefix' => '%DB_PREFIX%',    // Substituted with 'forum_'
    ],
    
    'Router' => [
        'class' => \ForkBB\Core\Router::class,
        'base_url' => '%BASE_URL%',   // Substituted with 'https://...'
    ],
],
Replace entire parameter value.
'dsn' => '%DB_DSN%'  // Replaces with actual DSN value
Embed parameters in strings.
'path' => '%DIR_ROOT%/public/uploads'  // Combines parameter + string
Reference other services with @service.
'csrf' => '@Csrf'  // Injects Csrf service
Access nested array values with dot notation.
'HMAC' => [
    'algo' => 'sha1',
    'salt' => 'random_salt',
],

'Secury' => [
    'class' => \ForkBB\Core\Secury::class,
    'hmac' => '%HMAC%',          // Entire array
    'algo' => '%HMAC.algo%',     // Just 'sha1'
],

Accessing Services

Basic Access

// Get service (lazy loaded)
$db = $c->DB;
$router = $c->Router;
$user = $c->user;

// Set service/parameter
$c->myService = new MyService();
$c->someValue = 123;

// Check if initialized
if ($c->isInit('DB')) {
    $c->DB->disconnect();
}

Nested Access

// Access nested parameters with dot notation
$algo = $c->{'HMAC.algo'};  // Get 'HMAC' => 'algo'
$value = $c->{'config.a.b'};  // Get nested array value

Dependency Injection

All services automatically receive the container:
class MyService
{
    public function __construct(protected Container $c)
    {
    }
    
    public function doSomething()
    {
        // Access other services
        $db = $this->c->DB;
        $user = $this->c->user;
        $router = $this->c->Router;
        
        // Access configuration
        $debug = $this->c->DEBUG;
        $baseUrl = $this->c->BASE_URL;
    }
}

Configuration Structure

Directory Paths

'shared' => [
    '%DIR_ROOT%'   => \realpath(__DIR__ . '/../..'),
    '%DIR_PUBLIC%' => '%DIR_ROOT%/public',
    '%DIR_APP%'    => '%DIR_ROOT%/app',
    '%DIR_CACHE%'  => '%DIR_APP%/cache',
    '%DIR_CONFIG%' => '%DIR_APP%/config',
    '%DIR_LANG%'   => '%DIR_APP%/lang',
    '%DIR_LOG%'    => '%DIR_APP%/log',
    '%DIR_VIEWS%'  => '%DIR_APP%/templates',
],

Core Services

'shared' => [
    'DB' => [
        'class'    => \ForkBB\Core\DB::class,
        'dsn'      => '%DB_DSN%',
        'username' => '%DB_USERNAME%',
        'password' => '%DB_PASSWORD%',
        'options'  => '%DB_OPTIONS%',
        'prefix'   => '%DB_PREFIX%',
    ],
    
    'Cache' => [
        'class'      => \ForkBB\Core\Cache\FileCache::class,
        'cache_dir'  => '%DIR_CACHE%',
        'reset_mark' => '%DB_DSN% %DB_PREFIX%',
    ],
    
    'Router' => [
        'class'    => \ForkBB\Core\Router::class,
        'base_url' => '%BASE_URL%',
        'csrf'     => '@Csrf',
    ],
    
    'View' => [
        'class'  => \ForkBB\Core\View::class,
        'config' => [
            'cache'      => '%DIR_CACHE%',
            'defaultDir' => '%DIR_VIEWS%/_default',
            'userDir'    => '%DIR_VIEWS%/_user',
        ],
    ],
],

Model Managers

'shared' => [
    'users'         => \ForkBB\Models\User\Users::class,
    'forums'        => '@ForumManager:init',
    'topics'        => \ForkBB\Models\Topic\Topics::class,
    'posts'         => \ForkBB\Models\Post\Posts::class,
    'groups'        => '@GroupManager:init',
    'categories'    => '@CategoriesManager:init',
    
    // Current user (via factory)
    'user'          => '@users:current',
    'userRules'     => '@UsersRules:init',
],

Service Lifecycle

1

Container Creation

Configuration loaded, parameters registered
2

First Access

Service definition located, dependencies resolved
3

Instantiation

Service class instantiated with container injected
4

Caching (Shared)

Instance stored for reuse
5

Subsequent Access

Cached instance returned (shared) or new instance created (multiple)

Example: Registering a Custom Service

Step 1: Create Service Class

app/MyExtension/MyService.php
namespace MyExtension;

use ForkBB\Core\Container;

class MyService
{
    public function __construct(protected Container $c)
    {
    }
    
    public function doSomething(): string
    {
        $user = $this->c->user;
        return "Hello, {$user->username}!";
    }
}

Step 2: Register in Container

config/main.php
'shared' => [
    // Simple registration
    'MyService' => \MyExtension\MyService::class,
    
    // With configuration
    'MyService' => [
        'class' => \MyExtension\MyService::class,
        'apiKey' => '%MY_API_KEY%',
        'cache' => '@Cache',
    ],
],

Step 3: Use Service

// In controller or other service
$result = $this->c->MyService->doSomething();

Best Practices

Always type hint the Container parameter.
public function __construct(protected Container $c) {}
Services are automatically lazy loaded. Don’t instantiate in constructor.
// Good - lazy loaded when needed
public function doWork() {
    $db = $this->c->DB;
}

// Bad - loaded immediately
public function __construct(protected Container $c) {
    $this->db = $c->DB;
}
Services that maintain state should be shared.
'shared' => [
    'DB' => ...,     // Connection should be shared
    'Cache' => ...,  // Cache should be shared
    'user' => ...,   // Current user should be shared
]
Domain models should be multiple instances.
'multiple' => [
    'Topic' => \ForkBB\Models\Topic\Topic::class,
    'Post' => \ForkBB\Models\Post\Post::class,
]
Check if expensive services are initialized before cleanup.
if ($c->isInit('DB')) {
    $c->DB->disconnect();
}

Advanced Features

Dynamic Service Registration

// Add configuration at runtime
$c->config([
    'shared' => [
        'NewService' => \My\NewService::class,
    ],
]);

Service Override

// Override existing service
$c->DB = new MyCustomDB();

// Or reconfigure
$c->config([
    'shared' => [
        'DB' => \My\CustomDB::class,
    ],
]);

Next Steps

Architecture

Understand how the container fits into ForkBB

Models

Create services that use models

Controllers

Access services in controllers