bindings[$id] = [ 'concrete' => $concrete, 'singleton' => $singleton ]; } /** * Bind a singleton dependency. */ public function singleton(string $id, callable|string $concrete): void { $this->bind($id, $concrete, true); } /** * Get a dependency from the container. */ public function get(string $id): mixed { if (isset($this->instances[$id])) { return $this->instances[$id]; } if (!$this->has($id)) { return $this->resolve($id); } $binding = $this->bindings[$id]; $concrete = $binding['concrete']; $object = $this->resolve($concrete); if ($binding['singleton']) { $this->instances[$id] = $object; } return $object; } /** * Check if a dependency is bound. */ public function has(string $id): bool { return isset($this->bindings[$id]) || isset($this->instances[$id]) || class_exists($id); } /** * Resolve a concrete class or binding. */ private function resolve(mixed $concrete): mixed { if ($concrete instanceof \Closure || is_callable($concrete)) { return $concrete($this); } if (is_string($concrete)) { if (!class_exists($concrete)) { return $concrete; } $reflector = new \ReflectionClass($concrete); if (!$reflector->isInstantiable()) { throw new Exception("Class {$concrete} is not instantiable."); } $constructor = $reflector->getConstructor(); if (null === $constructor) { return new $concrete(); } $parameters = $constructor->getParameters(); $dependencies = []; foreach ($parameters as $parameter) { $type = $parameter->getType(); if (!$type) { if ($parameter->isDefaultValueAvailable()) { $dependencies[] = $parameter->getDefaultValue(); } else { throw new Exception("Cannot resolve parameter {$parameter->getName()} in class {$concrete}"); } } elseif ($type instanceof \ReflectionUnionType) { throw new Exception("Union types in constructor injection not supported for class {$concrete}"); } else { $typeName = $type->getName(); $dependencies[] = $this->get($typeName); } } return $reflector->newInstanceArgs($dependencies); } return $concrete; } }