mirror of
https://gitee.com/hyperf/hyperf.git
synced 2024-12-04 20:58:13 +08:00
support closure injection
This commit is contained in:
parent
0dd3f05835
commit
345e69cfc1
42
src/di/src/AbstractCallableDefinitionCollector.php
Normal file
42
src/di/src/AbstractCallableDefinitionCollector.php
Normal 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,
|
||||
]);
|
||||
}
|
||||
}
|
41
src/di/src/ClosureDefinitionCollector.php
Normal file
41
src/di/src/ClosureDefinitionCollector.php
Normal 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;
|
||||
}
|
||||
}
|
19
src/di/src/ClosureDefinitionCollectorInterface.php
Normal file
19
src/di/src/ClosureDefinitionCollectorInterface.php
Normal 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;
|
||||
}
|
@ -23,6 +23,7 @@ class ConfigProvider
|
||||
return [
|
||||
'dependencies' => [
|
||||
MethodDefinitionCollectorInterface::class => MethodDefinitionCollector::class,
|
||||
ClosureDefinitionCollectorInterface::class => ClosureDefinitionCollector::class,
|
||||
],
|
||||
'commands' => [
|
||||
InitProxyCommand::class,
|
||||
|
@ -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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
40
src/di/tests/ClosureDefinitionTest.php
Normal file
40
src/di/tests/ClosureDefinitionTest.php
Normal 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());
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user