diff --git a/src/di/src/AbstractCallableDefinitionCollector.php b/src/di/src/AbstractCallableDefinitionCollector.php new file mode 100644 index 000000000..cc7472b49 --- /dev/null +++ b/src/di/src/AbstractCallableDefinitionCollector.php @@ -0,0 +1,42 @@ + $parameters + */ + protected function getDefinitionsFromParameters(array $parameters): array + { + $definitions = []; + foreach ($parameters as $parameter) { + $definitions[] = $this->createType( + $parameter->getName(), + $parameter->getType(), + $parameter->allowsNull(), + $parameter->isDefaultValueAvailable(), + $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null + ); + } + return $definitions; + } + + protected function createType($name, ?\ReflectionType $type, $allowsNull, $hasDefault = false, $defaultValue = null) + { + return new ReflectionType($type ? $type->getName() : 'mixed', $allowsNull, [ + 'defaultValueAvailable' => $hasDefault, + 'defaultValue' => $defaultValue, + 'name' => $name, + ]); + } +} diff --git a/src/di/src/ClosureDefinitionCollector.php b/src/di/src/ClosureDefinitionCollector.php new file mode 100644 index 000000000..735f034b6 --- /dev/null +++ b/src/di/src/ClosureDefinitionCollector.php @@ -0,0 +1,41 @@ +getParameters(); + + $definitions = $this->getDefinitionsFromParameters($parameters); + static::set($key, $definitions); + return $definitions; + } + + public function getReturnType(\Closure $closure): ReflectionType + { + $key = spl_object_hash($closure) . '@return'; + if (static::has($key)) { + return static::get($key); + } + $returnType = (new \ReflectionFunction($closure))->getReturnType(); + $type = $this->createType('', $returnType, $returnType ? $returnType->allowsNull() : true); + static::set($key, $type); + return $type; + } +} diff --git a/src/di/src/ClosureDefinitionCollectorInterface.php b/src/di/src/ClosureDefinitionCollectorInterface.php new file mode 100644 index 000000000..2ff4e7ad1 --- /dev/null +++ b/src/di/src/ClosureDefinitionCollectorInterface.php @@ -0,0 +1,19 @@ + [ MethodDefinitionCollectorInterface::class => MethodDefinitionCollector::class, + ClosureDefinitionCollectorInterface::class => ClosureDefinitionCollector::class, ], 'commands' => [ InitProxyCommand::class, diff --git a/src/di/src/MethodDefinitionCollector.php b/src/di/src/MethodDefinitionCollector.php index 358199c87..43c86149b 100644 --- a/src/di/src/MethodDefinitionCollector.php +++ b/src/di/src/MethodDefinitionCollector.php @@ -11,7 +11,7 @@ declare(strict_types=1); */ namespace Hyperf\Di; -class MethodDefinitionCollector extends MetadataCollector implements MethodDefinitionCollectorInterface +class MethodDefinitionCollector extends AbstractCallableDefinitionCollector implements MethodDefinitionCollectorInterface { /** * @var array @@ -72,17 +72,8 @@ class MethodDefinitionCollector extends MetadataCollector implements MethodDefin return static::get($key); } $parameters = ReflectionManager::reflectClass($class)->getMethod($method)->getParameters(); - $definitions = []; - foreach ($parameters as $parameter) { - $definitions[] = $this->createType( - $parameter->getName(), - $parameter->getType(), - $parameter->allowsNull(), - $parameter->isDefaultValueAvailable(), - $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null - ); - } + $definitions = $this->getDefinitionsFromParameters($parameters); static::set($key, $definitions); return $definitions; } @@ -98,13 +89,4 @@ class MethodDefinitionCollector extends MetadataCollector implements MethodDefin static::set($key, $type); return $type; } - - private function createType($name, ?\ReflectionType $type, $allowsNull, $hasDefault = false, $defaultValue = null) - { - return new ReflectionType($type ? $type->getName() : 'mixed', $allowsNull, [ - 'defaultValueAvailable' => $hasDefault, - 'defaultValue' => $defaultValue, - 'name' => $name, - ]); - } } diff --git a/src/di/tests/ClosureDefinitionTest.php b/src/di/tests/ClosureDefinitionTest.php new file mode 100644 index 000000000..6dfcfb421 --- /dev/null +++ b/src/di/tests/ClosureDefinitionTest.php @@ -0,0 +1,40 @@ +getParameters($closure); + $this->assertEquals(4, count($definitions)); + $this->assertEquals('int', $definitions[0]->getName()); + $this->assertFalse($definitions[0]->getMeta('defaultValueAvailable')); + $this->assertTrue($definitions[1]->getMeta('defaultValueAvailable')); + } + + public function testGetReturnTypes(){ + $collector = new ClosureDefinitionCollector(); + $closure = \Closure::fromCallable([new Foo(), 'getBar']); + $type = $collector->getReturnType($closure); + $this->assertEquals('mixed', $type->getName()); + } + + public function testGetParameterOfNoType() + { + $collector = new ClosureDefinitionCollector(); + $closure = \Closure::fromCallable([new Foo(), 'getFoo']); + /** @var ReflectionType[] $definitions */ + $definitions = $collector->getParameters($closure); + $this->assertEquals(1, count($definitions)); + $this->assertEquals('mixed', $definitions[0]->getName()); + } +} diff --git a/src/http-server/src/CoreMiddleware.php b/src/http-server/src/CoreMiddleware.php index 669b056ae..78bb9ddde 100644 --- a/src/http-server/src/CoreMiddleware.php +++ b/src/http-server/src/CoreMiddleware.php @@ -14,6 +14,7 @@ namespace Hyperf\HttpServer; use Closure; use FastRoute\Dispatcher; use Hyperf\Contract\NormalizerInterface; +use Hyperf\Di\ClosureDefinitionCollectorInterface; use Hyperf\Di\MethodDefinitionCollectorInterface; use Hyperf\HttpMessage\Stream\SwooleStream; use Hyperf\HttpServer\Contract\CoreMiddlewareInterface; @@ -52,6 +53,11 @@ class CoreMiddleware implements CoreMiddlewareInterface */ private $methodDefinitionCollector; + /** + * @var ClosureDefinitionCollectorInterface + */ + private $closureDefinitionCollector; + /** * @var NormalizerInterface */ @@ -63,6 +69,7 @@ class CoreMiddleware implements CoreMiddlewareInterface $this->dispatcher = $this->createDispatcher($serverName); $this->normalizer = $this->container->get(NormalizerInterface::class); $this->methodDefinitionCollector = $this->container->get(MethodDefinitionCollectorInterface::class); + $this->closureDefinitionCollector = $this->container->get(ClosureDefinitionCollectorInterface::class); } public function dispatch(ServerRequestInterface $request): ServerRequestInterface @@ -111,6 +118,11 @@ class CoreMiddleware implements CoreMiddlewareInterface return $this->methodDefinitionCollector; } + public function getClosureDefinitionCollector(): ClosureDefinitionCollectorInterface + { + return $this->closureDefinitionCollector; + } + public function getNormalizer(): NormalizerInterface { return $this->normalizer; @@ -130,7 +142,8 @@ class CoreMiddleware implements CoreMiddlewareInterface protected function handleFound(Dispatched $dispatched, ServerRequestInterface $request) { if ($dispatched->handler->callback instanceof Closure) { - $response = call($dispatched->handler->callback); + $parameters = $this->parseClosureParameters($dispatched->handler->callback, $dispatched->params); + $response = call($dispatched->handler->callback, $parameters); } else { [$controller, $action] = $this->prepareHandler($dispatched->handler->callback); $controllerInstance = $this->container->get($controller); @@ -138,7 +151,7 @@ class CoreMiddleware implements CoreMiddlewareInterface // Route found, but the handler does not exist. return $this->response()->withStatus(500)->withBody(new SwooleStream('Method of class does not exist.')); } - $parameters = $this->parseParameters($controller, $action, $dispatched->params); + $parameters = $this->parseMethodParameters($controller, $action, $dispatched->params); $response = $controllerInstance->{$action}(...$parameters); } return $response; @@ -218,15 +231,42 @@ class CoreMiddleware implements CoreMiddlewareInterface return Context::get(ResponseInterface::class); } + /** + * Keep it to maintain backward compatibility. Users may have extended core middleware. + */ + protected function parseParameters(string $controller, string $action, array $arguments): array + { + return $this->parseMethodParameters($controller, $action, $arguments); + } + /** * Parse the parameters of method definitions, and then bind the specified arguments or * get the value from DI container, combine to a argument array that should be injected * and return the array. */ - protected function parseParameters(string $controller, string $action, array $arguments): array + protected function parseMethodParameters(string $controller, string $action, array $arguments): array + { + $definitions = $this->getMethodDefinitionCollector()->getParameters($controller, $action); + return $this->getInjections($definitions, "{$controller}::{$action}", $arguments); + } + + /** + * Parse the parameters of closure definitions, and then bind the specified arguments or + * get the value from DI container, combine to a argument array that should be injected + * and return the array. + */ + protected function parseClosureParameters(Closure $closure, array $arguments): array + { + if (! $this->container->has(ClosureDefinitionCollectorInterface::class)) { + return []; + } + $definitions = $this->getClosureDefinitionCollector()->getParameters($closure); + return $this->getInjections($definitions, 'Closure', $arguments); + } + + private function getInjections(array $definitions, string $callableName, array $arguments): array { $injections = []; - $definitions = $this->getMethodDefinitionCollector()->getParameters($controller, $action); foreach ($definitions ?? [] as $pos => $definition) { $value = $arguments[$pos] ?? $arguments[$definition->getMeta('name')] ?? null; if ($value === null) { @@ -238,13 +278,12 @@ class CoreMiddleware implements CoreMiddlewareInterface $injections[] = $this->container->get($definition->getName()); } else { throw new \InvalidArgumentException("Parameter '{$definition->getMeta('name')}' " - . "of {$controller}::{$action} should not be null"); + . "of {$callableName} should not be null"); } } else { $injections[] = $this->getNormalizer()->denormalize($value, $definition->getName()); } } - return $injections; } }