resolveWithContext($className, []); } /** * Resolve and instantiate a class with specific context instances */ public function resolveWithContext(string $className, array $contextInstances = []): object { // Store context instances temporarily $originalContext = $this->contextInstances; $this->contextInstances = array_merge($this->contextInstances, $contextInstances); try { // Check cache first (but not when we have context overrides) if (empty($contextInstances) && isset($this->instanceCache[$className])) { return $this->instanceCache[$className]; } // Check if class exists if (!class_exists($className)) { throw new \InvalidArgumentException("Class {$className} does not exist"); } $reflectionClass = new ReflectionClass($className); // If class cannot be instantiated if (!$reflectionClass->isInstantiable()) { throw new \InvalidArgumentException("Class {$className} is not instantiable"); } $constructor = $reflectionClass->getConstructor(); // If no constructor, just instantiate if ($constructor === null) { $instance = new $className(); if (empty($contextInstances)) { $this->instanceCache[$className] = $instance; } return $instance; } // Resolve constructor dependencies $dependencies = []; foreach ($constructor->getParameters() as $parameter) { $dependencies[] = $this->resolveParameter($parameter); } // Create instance with resolved dependencies $instance = $reflectionClass->newInstanceArgs($dependencies); // Only cache if no context overrides if (empty($contextInstances)) { $this->instanceCache[$className] = $instance; } return $instance; } finally { // Restore original context $this->contextInstances = $originalContext; } } /** * Resolve a single parameter dependency */ private function resolveParameter(ReflectionParameter $parameter): mixed { $type = $parameter->getType(); // If no type hint, check if it has a default value if ($type === null) { if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } throw new \InvalidArgumentException("Cannot resolve parameter {$parameter->getName()} without type hint"); } // Handle union types (PHP 8+) if ($type instanceof \ReflectionUnionType) { throw new \InvalidArgumentException("Union types are not supported for parameter {$parameter->getName()}"); } $typeName = $type->getName(); // Check if we have a context instance for this type if (isset($this->contextInstances[$typeName])) { return $this->contextInstances[$typeName]; } // Check global context if (isset(self::$globalContext[$typeName])) { return self::$globalContext[$typeName]; } // Handle built-in types if ($type->isBuiltin()) { if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } throw new \InvalidArgumentException("Cannot resolve built-in type {$typeName} for parameter {$parameter->getName()}"); } // Always try to get from container first for all non-builtin types try { $container = Server::getContainer(); if ($container->has($typeName)) { return $container->get($typeName); } } catch (\Exception $e) { // Fall through to manual resolution } // Only try manual resolution for classes in our namespace if (strpos($typeName, 'KTXC\\') === 0) { try { return $this->resolve($typeName); } catch (\Exception $e) { // If still can't resolve and has default value, use it if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } // If parameter is nullable, return null if ($parameter->allowsNull()) { return null; } throw new \InvalidArgumentException("Cannot resolve dependency {$typeName} for parameter {$parameter->getName()}: " . $e->getMessage()); } } // For non-Ktrix classes that aren't in the container, fail gracefully if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } if ($parameter->allowsNull()) { return null; } throw new \InvalidArgumentException("Cannot resolve external dependency {$typeName} for parameter {$parameter->getName()}. This service should be registered in the container."); } /** * Resolve and instantiate a class with automatic context detection */ public function resolveWithAutoContext(string $className, object $contextSource = null): object { $contextInstances = []; if ($contextSource !== null) { // Use reflection to get all properties of the context source $reflection = new ReflectionClass($contextSource); $properties = $reflection->getProperties(); foreach ($properties as $property) { $property->setAccessible(true); $value = $property->getValue($contextSource); if (is_object($value)) { $contextInstances[get_class($value)] = $value; } } } return $this->resolveWithContext($className, $contextInstances); } /** * Smart resolve - automatically finds dependencies from the call stack */ public function smartResolve(string $className): object { $contextInstances = []; // Get the call stack to find potential context sources $backtrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 10); foreach ($backtrace as $frame) { if (isset($frame['object']) && is_object($frame['object'])) { $sourceObject = $frame['object']; $reflection = new ReflectionClass($sourceObject); $properties = $reflection->getProperties(); foreach ($properties as $property) { $property->setAccessible(true); $value = $property->getValue($sourceObject); if (is_object($value)) { $contextInstances[get_class($value)] = $value; } } } } return $this->resolveWithContext($className, $contextInstances); } /** * Clear the instance cache */ public function clearCache(): void { $this->instanceCache = []; } /** * Check if a class is cached */ public function isCached(string $className): bool { return isset($this->instanceCache[$className]); } }