Dependency Injection Container (PSR-11)
Larafony includes a powerful PSR-11 compliant dependency injection container with automatic autowiring.
Psr\Container\ContainerInterface standard for maximum interoperability.
What is a Container?
The dependency injection container is the heart of Larafony. It manages class dependencies and performs automatic dependency injection through constructor parameters. The container can:
- Autowire dependencies - Automatically resolve constructor dependencies
- Bind values - Store configuration values (strings, numbers, booleans)
- Register services - Store service instances or class names
- Dot notation access - Access nested values using dot syntax
Basic Usage
Accessing the Container
The container is available throughout your application. You can access it in several ways:
use Larafony\Framework\Web\Application;
use Psr\Container\ContainerInterface;
// Method 1: Through Application instance
$app = Application::instance();
$container = $app; // Application implements ContainerInterface
// Method 2: Dependency injection (preferred)
class UserController
{
public function __construct(
private readonly ContainerInterface $container
) {}
}
Retrieving Services
The get() method retrieves services from the container. If the service doesn't exist,
the container will attempt to autowire it:
use App\Services\EmailService;
// Get a service - will autowire if not registered
$emailService = $container->get(EmailService::class);
// Get with dot notation
$dbHost = $container->get('database.host');
Checking Service Existence
if ($container->has(EmailService::class)) {
$service = $container->get(EmailService::class);
}
Binding Values
Simple Value Bindings
Use bind() to store scalar values (strings, numbers, booleans, null):
// Bind configuration values
$container->bind('base_path', '/var/www/app');
$container->bind('debug', true);
$container->bind('timeout', 30);
// Retrieve bindings
$basePath = $container->getBinding('base_path'); // '/var/www/app'
$debug = $container->getBinding('debug'); // true
bind() only accepts scalar values. For objects or arrays, use set().
Registering Services
Using set()
The set() method registers services, instances, or any values:
use App\Services\EmailService;
use Larafony\Framework\Config\Contracts\ConfigContract;
// Set an instance
$config = new ConfigBase($container);
$container->set(ConfigContract::class, $config);
// Set a class name (will be autowired when retrieved)
$container->set('email', EmailService::class);
// Set with dot notation for nested values
$container->set('database.host', 'localhost');
$container->set('database.port', 3306);
Automatic Autowiring
How Autowiring Works
The container automatically resolves class dependencies by analyzing constructor parameters:
namespace App\Services;
use App\Repositories\UserRepository;
use Psr\Log\LoggerInterface;
class UserService
{
public function __construct(
private readonly UserRepository $repository,
private readonly LoggerInterface $logger
) {}
}
// Container automatically resolves dependencies
$userService = $container->get(UserService::class);
// Container will:
// 1. Resolve UserRepository (and its dependencies)
// 2. Resolve LoggerInterface (must be registered)
// 3. Instantiate UserService with both dependencies
Requirements for Autowiring
- Constructor parameters must be type-hinted with classes or interfaces
- Concrete classes are autowired automatically
- Interfaces must be registered in the container first
- Scalar parameters (string, int, etc.) cannot be autowired
Service Providers
Registering Services in Providers
Service providers are the recommended way to register services. They keep your bootstrap code organized:
namespace App\Providers;
use Larafony\Framework\Container\ServiceProvider;
use Psr\Log\LoggerInterface;
use App\Services\FileLogger;
class LoggerServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind interface to implementation
$this->container->set(
LoggerInterface::class,
new FileLogger('/var/log/app.log')
);
}
}
Register the provider in your bootstrap/app.php:
$app->registerProviders([
App\Providers\LoggerServiceProvider::class,
// ... other providers
]);
Practical Examples
Example: Repository Pattern
// Interface
interface UserRepositoryInterface
{
public function find(int $id): ?User;
}
// Implementation
class DatabaseUserRepository implements UserRepositoryInterface
{
public function __construct(
private readonly ConnectionContract $db
) {}
public function find(int $id): ?User
{
// Database query logic
}
}
// Register in provider
class RepositoryServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->container->set(
UserRepositoryInterface::class,
DatabaseUserRepository::class // Will be autowired
);
}
}
// Use in service
class UserService
{
public function __construct(
private readonly UserRepositoryInterface $repository
) {}
}
Best Practices
Do
- Use type-hinted constructor injection
- Register interfaces in service providers
- Keep services focused and single-purpose
- Use dot notation for configuration values
- Leverage autowiring for concrete classes
Don't
- Don't use the container as a service locator
- Don't register services directly in controllers
- Don't use constructor injection for optional dependencies
- Don't create circular dependencies
API Reference
| Method | Description | Returns |
|---|---|---|
get(string $id) |
Retrieve a service or autowire it | mixed |
has(string $id) |
Check if service is registered | bool |
set(string $key, mixed $value) |
Register a service or value | self |
bind(string $key, scalar $value) |
Bind a scalar value | void |
getBinding(string $key) |
Retrieve a bound scalar value | scalar |