PHP Dependency Injection Container — Complete Guide to PHP-DI and Autowiring
In this tutorial, you will learn about PHP Dependency Injection Container. We cover key concepts, practical examples, and best practices to help you master this topic.
PHP dependency injection containers like PHP-DI resolve class dependencies automatically through autowiring, with configuration via PHP attributes, definitions, and factory methods.
What You'll Learn
By the end of this tutorial, you'll configure PHP-DI, use autowiring with attributes, define services with factories, handle environment-specific configuration, and integrate DI in applications.
Why DI Containers Matter
Manual dependency wiring becomes unmanageable as applications grow. DI containers automate resolution, manage service lifecycles, and enable testing through dependency substitution.
Real-World Use
A REST API using PHP-DI autowires all controllers, services, and repositories. Swapping the database driver from MySQL to PostgreSQL requires changing only one definition.
DI Container Path
flowchart LR
A[PSR-11 Container] --> B[PHP-DI Container]
B --> C[PSR-17 HTTP Factory]
B --> D[Middleware Slim]
C --> E[Router PHP]
B --> F{You Are Here}
style F fill:#f90,color:#fff
PHP-DI Setup
Install and configure PHP-DI with autowiring.
<?php
use DI\ContainerBuilder;
require_once "vendor/autoload.php";
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$builder->useAttributes(true);
$container = $builder->build();
$service = $container->get(UserService::class);
Definition Files
Define service configurations in a PHP file.
<?php
// config/services.php
use function DI\autowire;
use function DI\get;
use function DI\create;
return [
PDO::class => function () {
return new PDO(
$_ENV["DB_DSN"] ?? "mysql:host=localhost;dbname=app",
$_ENV["DB_USER"] ?? "root",
$_ENV["DB_PASS"] ?? ""
);
},
LoggerInterface::class => autowire(Monolog\Logger::class)
->constructor("app", [get(Monolog\Handler\StreamHandler::class)]),
Monolog\Handler\StreamHandler::class => autowire()
->constructor("/var/log/app.log"),
UserRepository::class => autowire(),
UserService::class => autowire(),
];
Autowiring with Attributes
PHP-DI supports PHP 8 attributes for injection configuration.
<?php
use DI\Attribute\Injectable;
use Psr\Container\ContainerInterface;
#[Injectable]
class OrderService {
public function __construct(
private OrderRepository $repository,
private PaymentGateway $gateway,
private LoggerInterface $logger,
) {}
public function placeOrder(array $items): Order {
$this->logger->info("Placing order");
$order = $this->repository->create($items);
$this->gateway->charge($order->total);
return $order;
}
}
Factory Methods
Use factory methods for complex object creation.
<?php
use function DI\factory;
class PdoFactory {
public static function create(): PDO {
return new PDO(
sprintf("mysql:host=%s;dbname=%s", $_ENV["DB_HOST"], $_ENV["DB_NAME"]),
$_ENV["DB_USER"],
$_ENV["DB_PASS"],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
}
}
// config/services.php
return [
PDO::class => factory([PdoFactory::class, "create"]),
];
Environment-Specific Configuration
Use different configurations for development and production.
<?php
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$env = $_ENV["APP_ENV"] ?? "production";
$builder->addDefinitions(__DIR__ . "/config/services.php");
if ($env === "development") {
$builder->addDefinitions(__DIR__ . "/config/services_dev.php");
}
$container = $builder->build();
Common Mistakes
1. Overusing the Container as Service Locator
Inject the container into controllers instead of specific dependencies. This hides dependencies and makes testing harder.
2. Not Using Autowiring for Simple Cases
If a class has no special construction needs, autowire it. Manual definitions for everything is unnecessary.
3. Forgetting to Register Interface Bindings
Interfaces need explicit mapping to implementations. Autowiring works on concrete classes, not interfaces.
4. Circular Dependencies
A circular dependency chain causes infinite Recursion. Restructure to break the cycle.
5. Heavy Object Creation in Definitions
Factory closures should create and return objects, not perform heavy computation.
Practice Questions
1. What is autowiring?
Automatic resolution of constructor dependencies by type-hint, without manual configuration.
2. How do you bind an interface to an implementation?
$container->set(Interface::class, \DI\autowire(Implementation::class)) in definitions.
3. What is the difference between autowire() and create()?
autowire() resolves constructor arguments from the container. create() creates using provided arguments.
4. How do you handle multiple implementations of the same interface?
Use named bindings or tags to distinguish them. Inject using specific names.
5. Challenge: Create a DI container configuration for a multi-environment application.
<?php
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$builder->addDefinitions(__DIR__ . "/config/common.php");
if ($_ENV["APP_ENV"] === "test") {
$builder->addDefinitions(__DIR__ . "/config/test.php");
}
$container = $builder->build();
FAQ
Mini Project: Application Bootstrap with PHP-DI
Build an application bootstrap that uses PHP-DI for dependency injection.
<?php
use DI\ContainerBuilder;
use function DI\autowire;
use function DI\create;
class AppBootstrap {
private \DI\Container $container;
public function __construct() {
$builder = new ContainerBuilder();
$builder->useAutowiring(true);
$builder->useAttributes(true);
$builder->addDefinitions([
PDO::class => fn() => new PDO($_ENV["DB_DSN"], $_ENV["DB_USER"], $_ENV["DB_PASS"]),
LoggerInterface::class => fn() => new Monolog\Logger("app", [
new Monolog\Handler\StreamHandler("php://stdout"),
]),
]);
$this->container = $builder->build();
}
public function run(): void {
$controller = $this->container->get(AppController::class);
$controller->handle();
}
}
What's Next
PHP Middleware Slim PHP Router PHP PSR-11 Container
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro