definitions[$id] = $concrete; } /** * Get a dependency from the container. * * @param string $id * @return mixed * @throws Exception If dependency cannot be resolved. */ public function get(string $id) { if (isset($this->instances[$id])) { return $this->instances[$id]; } if (!$this->has($id)) { // Attempt auto-wiring if class exists if (class_exists($id)) { $this->definitions[$id] = $id; } else { throw new Exception(esc_html(sprintf('Target class [%s] does not exist and is not registered.', $id))); } } $concrete = $this->definitions[$id]; if ($concrete instanceof \Closure) { $instance = $concrete($this); } elseif (is_string($concrete) && class_exists($concrete)) { $instance = $this->resolve($concrete); } else { $instance = $concrete; } $this->instances[$id] = $instance; return $instance; } /** * Check if a dependency is registered. * * @param string $id * @return bool */ public function has(string $id): bool { return isset($this->definitions[$id]) || isset($this->instances[$id]); } /** * Resolve a class using reflection (auto-wiring). * * @param string $class * @return mixed * @throws Exception */ private function resolve(string $class) { $reflector = new ReflectionClass($class); if (!$reflector->isInstantiable()) { throw new Exception(esc_html(sprintf('Class [%s] is not instantiable.', $class))); } $constructor = $reflector->getConstructor(); if (null === $constructor) { return new $class; } $parameters = $constructor->getParameters(); $dependencies = $this->resolveDependencies($parameters); return $reflector->newInstanceArgs($dependencies); } /** * Resolve constructor dependencies. * * @param ReflectionParameter[] $parameters * @return array * @throws Exception */ private function resolveDependencies(array $parameters): array { $dependencies = []; foreach ($parameters as $parameter) { $type = $parameter->getType(); if (!$type || !($type instanceof \ReflectionNamedType) || $type->isBuiltin()) { if ($parameter->isDefaultValueAvailable()) { $dependencies[] = $parameter->getDefaultValue(); continue; } throw new Exception(esc_html(sprintf('Cannot resolve parameter [%s] in class constructor.', $parameter->getName()))); } $dependencies[] = $this->get($type->getName()); } return $dependencies; } }