Bind an interface or class name to a concrete implementation:
src/Providers/PluginServiceProvider.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?php
namespace YourVendorProviders;
use SefraContainer;
use SefraProvidersServiceProvider;
class PluginServiceProvider implements ServiceProvider
{
public function register(Container $container): void
{
// Bind interface to implementation
$container->bind(
LoggerInterface::class,
FileLogger::class
);
// Bind with a closure
$container->bind(DatabaseService::class, function($container) {
return new DatabaseService(
$container->get(ConfigInterface::class)
);
});
// Bind with parameters
$container->bind(ApiClient::class, function($c) {
return new ApiClient(
api_key: get_option('my_api_key'),
timeout: 30
);
});
}
public function boot(Container $container): void
{
// Boot logic can be added here if needed
}
}Retrieve services from the container:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Simple resolution
$logger = $container->get(LoggerInterface::class);
// Alternative method
$logger = $container->resolve(LoggerInterface::class);
// The container automatically resolves dependencies
class UserController {
public function __construct(
private LoggerInterface $logger,
private DatabaseService $db
) {}
}
// All dependencies are automatically injected
$controller = $container->get(UserController::class);
Register a service as a singleton to ensure only one instance exists:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Register as singleton
$container->singleton(DatabaseService::class);
// Singleton with closure
$container->singleton(CacheService::class, function($c) {
return new CacheService(
$c->get(ConfigInterface::class)
);
});
// Every call returns the same instance
$db1 = $container->get(DatabaseService::class);
$db2 = $container->get(DatabaseService::class);
// $db1 === $db2 (true)
When to Use Singletons:
When NOT to Use Singletons:
Bind an already-instantiated object:
1
2
3
4
5
6
$config = new Config(['api_key' => 'xyz123']);
$container->instance(ConfigInterface::class, $config);
// Useful for WordPress globals
global $wpdb;
$container->instance('wpdb', $wpdb);
Prevent the container from being injected as a dependency (recommended to avoid anti-patterns) is set by default.
1
2
3
4
5
6
7
8
9
// This will now throw an exception
class BadService {
public function __construct(Container $container) {
// ❌ Not allowed - promotes service locator anti-pattern
}
}
$container->get(BadService::class); // Throws exception
you can disable it if needed:
1
2
3
4
5
6
7
8
9
10
// This will prevent throwing an exception
$container->forbidContainerInjection(false);
class Service {
public function __construct(Container $container) {
}
}
$container->get(Service::class); // will works
Why Not recommend to prevent forbidding container injection:
Injecting the container itself (service locator pattern) defeats the purpose of dependency injection:
Instead, inject specific dependencies:
1
2
3
4
5
6
7
// ✅ Good - explicit dependencies
class GoodService {
public function __construct(
private LoggerInterface $logger,
private DatabaseService $db
) {}
}
Use closures for complex initialization logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Closure with multiple dependencies
$container->bind(EmailService::class, function($c) {
$service = new EmailService();
$service->setLogger($c->get(LoggerInterface::class));
$service->setTransport($c->get(EmailTransport::class));
$service->configure([
'from' => get_option('admin_email'),
'smtp_host' => get_option('smtp_host')
]);
return $service;
});
// Conditional binding
$container->bind(PaymentGateway::class, function($c) {
if (get_option('payment_mode') === 'test') {
return new TestPaymentGateway();
}
return new LivePaymentGateway(
$c->get(ApiClient::class)
);
});