support closure injection

This commit is contained in:
Reasno 2020-04-26 13:37:05 +08:00
parent 0dd3f05835
commit 345e69cfc1
7 changed files with 190 additions and 26 deletions

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Di;
abstract class AbstractCallableDefinitionCollector extends MetadataCollector
{
/**
* @param array<\ReflectionParameter> $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,
]);
}
}

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Di;
class ClosureDefinitionCollector extends AbstractCallableDefinitionCollector implements ClosureDefinitionCollectorInterface
{
public function getParameters(\Closure $closure): array
{
$key = spl_object_hash($closure);
if (static::has($key)) {
return static::get($key);
}
$reflectionFunction = new \ReflectionFunction($closure);
$parameters = $reflectionFunction->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;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Di;
interface ClosureDefinitionCollectorInterface
{
public function getParameters(\Closure $closure): array;
public function getReturnType(\Closure $closure): ReflectionType;
}

View File

@ -23,6 +23,7 @@ class ConfigProvider
return [
'dependencies' => [
MethodDefinitionCollectorInterface::class => MethodDefinitionCollector::class,
ClosureDefinitionCollectorInterface::class => ClosureDefinitionCollector::class,
],
'commands' => [
InitProxyCommand::class,

View File

@ -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,
]);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace HyperfTest\Di;
use Hyperf\Di\ClosureDefinitionCollector;
use Hyperf\Di\ReflectionType;
use HyperfTest\Di\Stub\Foo;
use PHPUnit\Framework\TestCase;
class ClosureDefinitionTest extends TestCase
{
public function testGetParameters(){
$collector = new ClosureDefinitionCollector();
$closure = \Closure::fromCallable([new Foo(), 'getBar']);
$definitions = $collector->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());
}
}

View File

@ -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;
}
}