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

Walkthrough

Space play step click any line
Three takeaways
  1. 1A container decouples object creation from usage by mapping abstract names to factories or reflected classes.
  2. 2Reflection lets you inspect constructor signatures and recursively resolve typed dependencies without manual wiring.
  3. 3Caching resolved instances behind a closure turns any binding into a lazily-created singleton.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a DI container with reflection in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code