diff --git a/src/Container.php b/src/Container.php index a37055d..cc3f8d9 100755 --- a/src/Container.php +++ b/src/Container.php @@ -6,6 +6,9 @@ use Closure; use Iterator; +use ReflectionClass; +use ReflectionNamedType; +use RuntimeException; use Psr\Container\ContainerInterface; class Container implements ContainerInterface, Iterator @@ -71,6 +74,70 @@ public function get(string $id, ...$parameters): mixed return $this->instances[$id]; } + /** + * 构造器注入:按参数类型从容器解析依赖,未注册的类递归构建。 + */ + public function make(string $class): object + { + return $this->build($class, []); + } + + private function build(string $class, array $resolving): object + { + if ($this->has($class)) { + return $this->get($class); + } + + if (!class_exists($class)) { + throw new RuntimeException(sprintf('Container cannot make unknown class "%s".', $class)); + } + + if (in_array($class, $resolving, true)) { + throw new RuntimeException(sprintf( + 'Circular dependency detected: %s.', + implode(' -> ', [...$resolving, $class]) + )); + } + + $resolving[] = $class; + $reflection = new ReflectionClass($class); + $constructor = $reflection->getConstructor(); + + if ($constructor === null || $constructor->getNumberOfParameters() === 0) { + return $reflection->newInstance(); + } + + $arguments = []; + + foreach ($constructor->getParameters() as $parameter) { + $type = $parameter->getType(); + + if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) { + $name = $type->getName(); + $arguments[] = $this->has($name) ? $this->get($name) : $this->build($name, $resolving); + continue; + } + + if ($parameter->isDefaultValueAvailable()) { + $arguments[] = $parameter->getDefaultValue(); + continue; + } + + if ($parameter->allowsNull()) { + $arguments[] = null; + continue; + } + + throw new RuntimeException(sprintf( + 'Cannot resolve parameter $%s of %s::__construct().', + $parameter->getName(), + $class + )); + } + + return $reflection->newInstanceArgs($arguments); + } + public function clear(string $id): void { if (array_key_exists($id, $this->instances)) { diff --git a/tests/MakeTest.php b/tests/MakeTest.php new file mode 100644 index 0000000..9083bd6 --- /dev/null +++ b/tests/MakeTest.php @@ -0,0 +1,87 @@ +add(DemoDependency::class, $dependency); + + $controller = $container->make(DemoController::class); + + $this->assertInstanceOf(DemoController::class, $controller); + $this->assertSame($dependency, $controller->service->dependency); + } + + + public function testInterfaceBindingIsInjected(): void + { + $container = new Container(); + $container->add(DemoContract::class, new DemoImplementation()); + + $consumer = $container->make(DemoConsumer::class); + + $this->assertInstanceOf(DemoImplementation::class, $consumer->contract); + } + + public function testCircularDependencyIsDetected(): void + { + $this->expectException(RuntimeException::class); + + (new Container())->make(DemoCircularA::class); + } +}