TL;DR — Constructor Property Promotion (PHP 8), typed properties, and PSR-11
DI containers let you inject services in one line per dependency.
Less boilerplate ⇢ more readable code ⇢ happier developers.
1- Why Dependency Injection at all?
- Loose coupling – swap an implementation (e.g. a logger) without touching business logic.
- Testability – pass a fake or mock object and run fast unit tests.
- Single Responsibility – classes specify what they need, not where to find it.
Classic PHP achieved this via manual setters or verbose constructors.
Modern PHP gives us language features that turn DI into a one-liner.
2- Constructor Property Promotion (PHP 8)
Before PHP 8 you needed three steps per dependency:
class ReportController { private LoggerInterface $logger; public function __construct(LoggerInterface $logger) { $this->logger = $logger; } } Since PHP 8 you can declare + type + assign in the constructor signature:
class ReportController { public function __construct(private LoggerInterface $logger) {} } That’s one line instead of four, and the property is ready everywhere in the class.
Visibility & immutability
class ExportService { public function __construct( public readonly CsvWriter $csvWriter, private readonly StorageAdapter $storage ) {} } -
public|protected|privateis required. -
readonly(PHP 8.1) forbids re-assignment after construction → safer objects.
3- Typed Properties (PHP 7.4+)
Typed properties eliminate PHPDoc duplication:
private CacheItemPoolInterface $cache; // real type, enforced by engine Combine with promotion for concise & type-safe DI.
4- Using a PSR-11 Container
A container builds objects and resolves their dependencies.
Most frameworks come with one (Illuminate\Container, Symfony DI, Laminas DI), but you can stay framework-agnostic with league/container:
composer require league/container use League\Container\Container; use Psr\Log\LoggerInterface; use Monolog\Logger; use Monolog\Handler\StreamHandler; $container = new Container(); /* 1. register definitions */ $container->add(LoggerInterface::class, function () { $log = new Logger('app'); $log->pushHandler(new StreamHandler('php://stdout')); return $log; }); /* 2. define a service using constructor promotion */ class Greeter { public function __construct(private LoggerInterface $logger) {} public function greet(string $name): void { $this->logger->info("Hello {$name}"); } } /* 3. resolve & use */ $greeter = $container->get(Greeter::class); $greeter->greet('DEV Community'); The container inspects the Greeter constructor, sees a LoggerInterface
parameter, and supplies the registered instance.
5- “Plain Old PHP” Without a Container
For micro-scripts a full container can feel heavy.
You can still reap promotion benefits by wiring dependencies by hand:
$logger = new Logger('cli'); $cliTool = new CliTool($logger); // promotion assigns $logger Small project? Acceptable. Bigger code-base? Prefer a container or framework
so wiring logic isn’t scattered everywhere.
6- Best Practices
| ✅ Do | ❌ Avoid |
|---|---|
| Type-hint every promoted property. | mixed properties (missing type). |
| Limit constructor to ~5 deps – else refactor. | God objects with 10+ services. |
Use readonly for immutable deps. | Reassigning promoted properties. |
| Document constructor params via PHP promoted syntax, not PHPDoc. | Redundant @var tags that drift out of sync. |
7- Pitfalls to Watch For
- Circular dependencies – containers will throw “circular reference” errors.
- Lazy services – if a service is heavy (e.g., DB connection), register it as a factory or use lazy proxies.
- Visibility mismatch – private is inaccessible to child classes; choose protected if subclasses need the dependency.
8- Conclusion
Constructor property promotion + typed properties erase the ceremony around
dependency injection. When paired with a PSR-11 container you get:
- Cleaner constructors
- Engine-enforced types
- Seamless test doubles
Next time you spin up a PHP 8 project, try this pattern—you’ll never look back.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.