Skip to main content
The Container class is ForkBB’s dependency injection container, providing service resolution, shared instances, and parameter management.

Overview

Based on artoodetoo/container, the Container manages:
  • Shared services - Singleton instances created once
  • Multiple services - New instances created on each access
  • Parameters - Configuration values and data
  • Factory methods - Services created via factory patterns
The Container is available throughout ForkBB via constructor injection, typically as $c or $this->c.

Constructor

public function __construct(array $config = [])
config
array
default:"[]"
Container configuration with optional keys:
  • shared - Array of shared service definitions
  • multiple - Array of multiple instance service definitions
  • Other keys become instance parameters
Example:
$container = new Container([
    'shared' => [
        'db' => [DB::class, $dsn, $username, $password],
        'router' => [Router::class, '%base.url%', '@csrf']
    ],
    'multiple' => [
        'validator' => Validator::class
    ],
    'base.url' => 'https://example.com',
    'debug' => true
]);

Service Resolution

__get

public function __get(string $key): mixed
Retrieves a service or parameter from the container.
key
string
required
Service name or parameter path. Supports dot notation for nested values (e.g., config.base.url).
return
mixed
The resolved service instance or parameter value.
Service Resolution Rules:
  1. Existing instances - Returns cached value immediately
  2. Dot notation - Traverses arrays/objects (e.g., config.db.host)
  3. Shared services - Creates and caches singleton
  4. Multiple services - Creates new instance each time
  5. Parameter substitution - Resolves %param% pattern (e.g., %base.url%)
Examples:
// Access shared service (singleton)
$db = $this->c->DB;
$router = $this->c->Router;

// Access nested configuration
$dbHost = $this->c->{'config.db.host'};
$debugMode = $this->c->{'config.debug'};

// Create new validator instance
$validator = $this->c->Validator;
Factory Method Pattern:
// Service definition using factory
'shared' => [
    'cache' => '@factory:createCache'
]

// Factory class
class Factory {
    public function createCache($container) {
        return new Cache($container->config);
    }
}

// Usage
$cache = $this->c->cache;

__set

public function __set(string $key, mixed $service): void
Sets a service or parameter value.
key
string
required
Service name. Cannot contain dots.
service
mixed
required
The service instance or parameter value to store.
Example:
// Store a service instance
$this->c->myService = new MyService();

// Store a parameter
$this->c->apiKey = 'secret-key-123';

Configuration

config

public function config(array $config): void
Merges additional configuration into the container.
config
array
required
Configuration array with same structure as constructor.
Example:
$this->c->config([
    'shared' => [
        'mailer' => [Mailer::class, '%smtp.host%']
    ],
    'smtp.host' => 'smtp.example.com'
]);

setParameter

public function setParameter(string $name, mixed $value): Container
Sets a nested parameter using dot notation.
name
string
required
Parameter path with dot notation (e.g., db.host).
value
mixed
required
The parameter value.
return
Container
Returns self for method chaining.
Example:
$this->c
    ->setParameter('db.host', 'localhost')
    ->setParameter('db.port', 3306)
    ->setParameter('db.charset', 'utf8mb4');

// Access parameters
$host = $this->c->{'db.host'}; // 'localhost'

Utility Methods

fromArray

public function fromArray(array $array, array $tree): mixed
Retrieves a value from a nested array.
array
array
required
The source array.
tree
array
required
Array of keys representing the path.
return
mixed
The value at the path, or null if not found.
Example:
$data = [
    'user' => [
        'profile' => [
            'name' => 'John Doe'
        ]
    ]
];

$name = $this->c->fromArray($data, ['user', 'profile', 'name']);
// Returns: 'John Doe'

isInit

public function isInit(string $name): bool
Checks if a service has been initialized.
name
string
required
Service name.
return
bool
True if the service instance exists in cache.
Example:
if ($this->c->isInit('DB')) {
    // Database connection already established
    $queries = $this->c->DB->getQueries();
}

Parameter Substitution

The Container supports automatic parameter substitution:

Full Substitution

Replaces entire string and returns any type:
'shared' => [
    'router' => [Router::class, '%base.url%'] // Returns actual value type
]

Partial Substitution

Replaces within string, always returns string:
'dsn' => 'mysql:host=%db.host%;dbname=%db.name%'
// Result: 'mysql:host=localhost;dbname=forkbb'

Service Reference

References another service using @ prefix:
'shared' => [
    'validator' => [Validator::class, '@secury', '@db']
]

// Access nested service properties
'config.version' => '@app.version'

Real-World Examples

Accessing Core Services

~/workspace/source/app/Controllers/Routing.php
class Routing
{
    public function __construct(protected Container $c)
    {
    }

    public function routing(): Page
    {
        // Access shared services
        $user = $this->c->user;
        $config = $this->c->config;
        $router = $this->c->Router;

        // Use services
        $router->add(
            $router::GET,
            '/forum/{id|i:[1-9]\d*}/{name}',
            'Forum:view',
            'Forum'
        );

        return $page;
    }
}

Service Configuration

// Define services
$container->config([
    'shared' => [
        'db' => [
            DB::class,
            'dsn' => '%db.dsn%',
            'username' => '%db.user%',
            'password' => '%db.pass%',
            'prefix' => '%db.prefix%'
        ],
        'router' => [Router::class, '%base.url%', '@csrf'],
        'validator' => [Validator::class, '@container']
    ],
    'db.dsn' => 'mysql:host=localhost;dbname=forkbb',
    'db.user' => 'root',
    'db.pass' => 'secret',
    'db.prefix' => 'fork_',
    'base.url' => 'https://example.com/forum'
]);

Creating Multiple Instances

// Each access creates a new instance
$container->config([
    'multiple' => [
        'validator' => Validator::class
    ]
]);

$validator1 = $this->c->Validator; // New instance
$validator2 = $this->c->Validator; // Another new instance

Error Handling

The Container throws InvalidArgumentException for:
  • Accessing undefined services
  • Using dots in __set() keys
  • Setting parameters on scalar values in path
Example:
try {
    $service = $this->c->nonExistentService;
} catch (InvalidArgumentException $e) {
    // Handle: "Wrong property name: nonExistentService"
}

Best Practices

Use Type Hints

public function __construct(protected Container $c)
Always inject the Container via constructor with type hints.

Prefer Shared Services

Use shared for services that maintain state or are expensive to create (database, router, etc.).

Check Initialization

if ($this->c->isInit('DB')) {
    // Use existing connection
}
Check if expensive services are initialized before accessing.

Use Parameter Substitution

'%base.url%/api' // Good
$container->{'base.url'} . '/api' // Works but verbose
Leverage parameter substitution for cleaner configuration.

See Also