bindings[$name] = $value; } public function instance(string $name, mixed $value): void { $this->instances[$name] = $value; } public function make(string $className): object { if (isset($this->bindings[$className])) { $binding = $this->bindings[$className]; if (is_string($binding) && is_a($binding, $className, true)) { return $this->instantiate($binding); } return $binding; } if (isset($this->instances[$className])) { return $this->instances[$className]; } return $this->instantiate($className); } public function clear(): void { $this->bindings = []; $this->instances = []; } public function get(string $name): mixed { return $this->instances[$name] ?? $this->bindings[$name] ?? null; } public function call(callable|array|string $handler, array $parameters = []): mixed { if (is_array($handler)) { return $this->callMethod($handler, $parameters); } if (is_callable($handler)) { return $this->callFunction($handler, $parameters); } throw new Exception('Invalid handler.'); } protected function callFunction(callable $handler, array $parameters): mixed { $reflection = new ReflectionFunction($handler); return $reflection->invokeArgs($this->resolveArgs($reflection, $parameters)); } protected function callMethod(array $handler, array $parameters): mixed { [$class, $method] = $handler; if (is_string($class)) { $class = new $class(); } $reflection = new ReflectionMethod($class, $method); return $reflection->invokeArgs($class, $this->resolveArgs($reflection, $parameters)); } protected function resolveArgs(\ReflectionFunctionAbstract $reflection, array $parameters): array { $args = []; foreach ($reflection->getParameters() as $param) { $name = $param->getName(); if (array_key_exists($name, $parameters)) { $args[] = $parameters[$name]; continue; } $type = $param->getType(); if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) { $typeName = $type->getName(); if (array_key_exists($typeName, $this->instances)) { $args[] = $this->instances[$typeName]; continue; } if (array_key_exists($typeName, $this->bindings)) { $binding = $this->bindings[$typeName]; if (is_string($binding) && is_a($binding, $typeName, true)) { $args[] = $this->instantiate($binding); continue; } $args[] = $binding; continue; } } $args[] = $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null; } return $args; } /** * @param class-string $className */ protected function instantiate(string $className): object { $reflection = new ReflectionClass($className); if (!$reflection->isInstantiable()) { throw new Exception("Cannot instantiate {$className}: class is not instantiable."); } $constructor = $reflection->getConstructor(); if ($constructor === null) { return new $className(); } $args = $this->resolveArgs($constructor, []); return $reflection->newInstanceArgs($args); } }