Minimal Dependency Injection Container
PHP Code Editor
Execution Result
Ready to execute
Click the "Run Script" button to see the output here
Description
A dependency injection container removes the need to manually pass dependencies through every layer of your application. Instead, you register how each class should be built, and the container resolves the full dependency graph when you ask for an instance.
bind() registers a factory that produces a new instance on every make() call. singleton() registers a factory that runs only once and caches the result so all callers share the same instance. instance() registers a pre-built object directly. When none of those match, autoWire() uses ReflectionClass to inspect the constructor parameters, recursively calls make() for each typed parameter, and instantiates the class with the resolved dependencies. This means classes with type-hinted constructors can be resolved without any manual registration at all.
<?php
/**
* Minimal dependency injection container.
* Supports: bind(), singleton(), make(), auto-wiring via reflection.
*/
class Container
{
private array $bindings = [];
private array $singletons = [];
private array $instances = [];
public function bind(string $abstract, callable $factory): void
{
$this->bindings[$abstract] = $factory;
}
public function singleton(string $abstract, callable $factory): void
{
$this->singletons[$abstract] = $factory;
}
public function instance(string $abstract, object $instance): void
{
$this->instances[$abstract] = $instance;
}
public function make(string $abstract): mixed
{
// Pre-bound instance
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
// Singleton
if (isset($this->singletons[$abstract])) {
if (!isset($this->instances["singleton.$abstract"])) {
$this->instances["singleton.$abstract"] = ($this->singletons[$abstract])($this);
}
return $this->instances["singleton.$abstract"];
}
// Factory binding
if (isset($this->bindings[$abstract])) {
return ($this->bindings[$abstract])($this);
}
// Auto-wire via reflection
return $this->autoWire($abstract);
}
private function autoWire(string $class): object
{
$ref = new ReflectionClass($class);
$ctor = $ref->getConstructor();
if ($ctor === null) return $ref->newInstance();
$args = array_map(
fn(ReflectionParameter $p) => $this->make($p->getType()->getName()),
$ctor->getParameters()
);
return $ref->newInstanceArgs($args);
}
}
// Demo
interface LoggerInterface
{
public function log(string $message): void;
}
class FileLogger implements LoggerInterface
{
public function log(string $message): void
{
echo "[FileLogger] $message\n";
}
}
class UserRepository
{
public function __construct(private readonly LoggerInterface $logger) {}
public function find(int $id): array
{
$this->logger->log("Finding user $id");
return ['id' => $id, 'name' => "User $id"];
}
}
class UserService
{
public function __construct(private readonly UserRepository $repo) {}
public function getUser(int $id): array
{
return $this->repo->find($id);
}
}
$container = new Container();
$container->singleton(LoggerInterface::class, fn() => new FileLogger());
$container->bind(UserRepository::class, fn($c) => new UserRepository($c->make(LoggerInterface::class)));
$container->bind(UserService::class, fn($c) => new UserService($c->make(UserRepository::class)));
$service = $container->make(UserService::class);
$user = $service->getUser(42);
echo "Result: " . json_encode($user) . "\n";
// Same logger instance (singleton)
$l1 = $container->make(LoggerInterface::class);
$l2 = $container->make(LoggerInterface::class);
var_dump($l1 === $l2); // true
Comments
No comments yet
Be the first to share your thoughts!