From 4bb90143d54fa6cdc65fa531531b20194be1110d Mon Sep 17 00:00:00 2001 From: JanHuang <384099566@qq.com> Date: Mon, 26 Oct 2026 23:48:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(container):=20=E6=96=B0=E5=A2=9E=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=99=A8=E6=B3=A8=E5=85=A5=20make()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Container::make() 按构造参数类型递归解析依赖 - 支持已注册服务、接口绑定、默认值与 nullable 参数 - 检测循环依赖并提示依赖链 - 新增 DI / 接口绑定 / 循环依赖测试 --- src/Container.php | 67 +++++++++++++++++++++++++++++++++++ tests/MakeTest.php | 87 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 tests/MakeTest.php 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); + } +}