php
65 lines · 9 steps
Building a DI container with reflection in PHP
A dependency injection container that registers factories and auto-wires constructor dependencies using reflection.
Explained by
highlit
1final class Container
2{
3 private array $bindings = [];
4 private array $instances = [];
5
6 public function bind(string $abstract, callable $factory): void
7 {
8 $this->bindings[$abstract] = $factory;
9 }
10
11 public function singleton(string $abstract, callable $factory): void
12 {
13 $this->bindings[$abstract] = function (Container $c) use ($abstract, $factory) {
14 return $this->instances[$abstract] ??= $factory($c);
15 };
16 }
17
18 public function make(string $abstract): object
19 {
20 if (isset($this->bindings[$abstract])) {
21 return ($this->bindings[$abstract])($this);
22 }
23
24 return $this->resolve($abstract);
25 }
26
27 private function resolve(string $class): object
28 {
29 $reflector = new ReflectionClass($class);
30
31 if (!$reflector->isInstantiable()) {
32 throw new RuntimeException("Cannot instantiate {$class}");
33 }
34
35 $constructor = $reflector->getConstructor();
36
37 if ($constructor === null) {
38 return new $class();
39 }
40
41 $arguments = array_map(
42 fn (ReflectionParameter $param) => $this->resolveParameter($param, $class),
43 $constructor->getParameters()
44 );
45
46 return $reflector->newInstanceArgs($arguments);
47 }
48
49 private function resolveParameter(ReflectionParameter $param, string $class): mixed
50 {
51 $type = $param->getType();
52
53 if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
54 return $this->make($type->getName());
55 }
56
57 if ($param->isDefaultValueAvailable()) {
58 return $param->getDefaultValue();
59 }
60
61 throw new RuntimeException(
62 "Unresolvable dependency \${$param->getName()} in {$class}"
63 );
64 }
65}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1A container decouples object creation from usage by mapping abstract names to factories or reflected classes.
- 2Reflection lets you inspect constructor signatures and recursively resolve typed dependencies without manual wiring.
- 3Caching resolved instances behind a closure turns any binding into a lazily-created singleton.
Related explainers
java
@AutoConfiguration @EnableConfigurationProperties(RateLimiterProperties.class) @ConditionalOnClass(RateLimiter.class) public class RateLimiterAutoConfiguration {
Building a Spring Boot rate-limiter auto-config
auto-configuration
conditional-beans
rate-limiting
Intermediate
7 steps
php
<?php namespace App\Providers;
Defining feature flags with Laravel Pennant
feature-flags
service-provider
config-driven
Intermediate
6 steps
php
<?php namespace App\Http\Controllers;
Email confirmation with signed URLs in Laravel
signed-urls
email-verification
middleware
Intermediate
6 steps
php
<?php namespace App\RateLimiting;
Sliding-window rate limiting with Redis sorted sets
rate-limiting
redis
sorted-sets
Advanced
8 steps
php
<?php namespace App\Models\Concerns;
How an Auditable trait logs Eloquent changes in Laravel
traits
model-events
polymorphic-relations
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Server-Sent Events in Laravel
server-sent-events
streaming
long-polling
Advanced
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-di-container-with-reflection-in-php-explained-php-43a3/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.