Back to Portfolio

Features

Binding Services

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 } }

Resolving Services

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);

Singleton Bindings

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:

Binding Concrete Instances

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);

Forbidding Container Injection

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 ) {} }

Working with Closures

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) ); });