Merge pull request #457 from limingxinleo/1.1-di

Optimized code for di.
This commit is contained in:
李铭昕 2019-08-27 19:29:34 +08:00 committed by GitHub
commit 47e53de968
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 534 additions and 50 deletions

View File

@ -44,5 +44,5 @@ before_script:
- composer config -g process-timeout 900 && composer update
script:
- composer analyse
- composer analyse src/di src/json-rpc
- composer test

View File

@ -272,7 +272,7 @@
"license-check": "docheader check src/ test/",
"cs-fix": "php-cs-fixer fix $1",
"json-fix": "./bin/composer-json-fixer",
"analyse": "phpstan analyse --memory-limit 300M -l 5 -c phpstan.neon ./src/json-rpc"
"analyse": "phpstan analyse --memory-limit 300M -l 3 -c phpstan.neon"
},
"minimum-stability": "dev",
"prefer-stable": true

View File

@ -1,6 +1,92 @@
# Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :)
# Fortunately, You can ingore it by the following config.
#
rules:
- PHPStan\Rules\Arrays\DeadForeachRule
- PHPStan\Rules\Comparison\BooleanOrConstantConditionRule
- PHPStan\Rules\Comparison\ElseIfConstantConditionRule
- PHPStan\Rules\Comparison\IfConstantConditionRule
- PHPStan\Rules\Comparison\TernaryOperatorConstantConditionRule
parameters:
bootstrap: "bootstrap.php"
checkFunctionArgumentTypes: true
checkArgumentsPassedByReference: true
featureToggles:
deadCatchesRule: false
noopRule: false
tooWideTypehints: false
unreachableStatement: false
ignoreErrors:
- "#will always evaluate to false#"
excludes_analyse:
- %currentWorkingDirectory%/src/*/tests/*
- %currentWorkingDirectory%/src/*/tests/*
conditionalTags:
PHPStan\Rules\Exceptions\DeadCatchRule:
phpstan.rules.rule: %featureToggles.deadCatchesRule%
PHPStan\Rules\DeadCode\NoopRule:
phpstan.rules.rule: %featureToggles.noopRule%
PHPStan\Rules\DeadCode\UnreachableStatementRule:
phpstan.rules.rule: %featureToggles.unreachableStatement%
PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule:
phpstan.rules.rule: %featureToggles.tooWideTypehints%
PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule:
phpstan.rules.rule: %featureToggles.tooWideTypehints%
PHPStan\Rules\TooWideTypehints\TooWidePrivateMethodReturnTypehintRule:
phpstan.rules.rule: %featureToggles.tooWideTypehints%
services:
-
class: PHPStan\Rules\Classes\ImpossibleInstanceOfRule
arguments:
checkAlwaysTrueInstanceof: %checkAlwaysTrueInstanceof%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeFunctionCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeMethodCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\ImpossibleCheckTypeStaticMethodCallRule
arguments:
checkAlwaysTrueCheckTypeFunctionCall: %checkAlwaysTrueCheckTypeFunctionCall%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Comparison\StrictComparisonOfDifferentTypesRule
arguments:
checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison%
tags:
- phpstan.rules.rule
-
class: PHPStan\Rules\Exceptions\DeadCatchRule
-
class: PHPStan\Rules\DeadCode\NoopRule
-
class: PHPStan\Rules\DeadCode\UnreachableStatementRule
-
class: PHPStan\Rules\TooWideTypehints\TooWideClosureReturnTypehintRule
-
class: PHPStan\Rules\TooWideTypehints\TooWideFunctionReturnTypehintRule
-
class: PHPStan\Rules\TooWideTypehints\TooWidePrivateMethodReturnTypehintRule

View File

@ -21,9 +21,6 @@ use Hyperf\Di\Aop\AroundInterface;
*/
class Aspect extends AbstractAnnotation
{
/**
* {@inheritdoc}
*/
public function collectClass(string $className): void
{
// @TODO Add order property.
@ -33,7 +30,9 @@ class Aspect extends AbstractAnnotation
$instance = $instantitor->instantiate($className);
switch ($instance) {
case $instance instanceof AroundInterface:
AspectCollector::setAround($className, $instance->classes, $instance->annotations);
$classes = property_exists($instance, 'classes') ? $instance->classes : [];
$annotations = property_exists($instance, 'annotations') ? $instance->annotations : [];
AspectCollector::setAround($className, $classes, $annotations);
break;
}
}

View File

@ -28,8 +28,8 @@ class AspectCollector extends MetadataCollector
public static function setAround(string $aspect, array $classes, array $annotations): void
{
$classes && static::set('classes.' . $aspect, $classes);
$annotations && static::set('annotations.' . $aspect, $annotations);
static::set('classes.' . $aspect, $classes);
static::set('annotations.' . $aspect, $annotations);
static::$aspectRules[$aspect] = [
'classes' => $classes,
'annotations' => $annotations,

View File

@ -14,6 +14,7 @@ namespace Hyperf\Di\Aop;
use Closure;
use Hyperf\Di\Annotation\AnnotationCollector;
use Hyperf\Di\Exception\Exception;
class ProceedingJoinPoint
{
@ -43,7 +44,7 @@ class ProceedingJoinPoint
public $originalMethod;
/**
* @var Closure
* @var null|Closure
*/
public $pipe;
@ -61,6 +62,10 @@ class ProceedingJoinPoint
public function process()
{
$closure = $this->pipe;
if (! $closure instanceof Closure) {
throw new Exception('The pipe is not instanceof \Closure');
}
return $closure($this);
}

View File

@ -52,7 +52,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract
];
/**
* @var Identifier
* @var null|Identifier
*/
private $class;
@ -78,7 +78,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract
continue;
}
if (! $namespace instanceof Namespace_) {
return;
break;
}
// Add current class namespace.
$usedNamespace = [
@ -112,6 +112,8 @@ class ProxyCallVisitor extends NodeVisitorAbstract
}
}
}
return null;
}
public function leaveNode(Node $node)
@ -134,7 +136,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract
break;
case $node instanceof StaticPropertyFetch && $this->extends:
// Rewrite parent::$staticProperty to ParentClass::$staticProperty.
if ($node->class && $node->class->toString() === 'parent') {
if ($node->class instanceof Node\Name && $node->class->toString() === 'parent') {
$node->class = new Name($this->extends->toCodeString());
return $node;
}
@ -225,17 +227,17 @@ class ProxyCallVisitor extends NodeVisitorAbstract
$class = $this->class->toString();
$staticCall = new StaticCall(new Name('self'), '__proxyCall', [
// OriginalClass::class
new ClassConstFetch(new Name($class), new Identifier('class')),
new Node\Arg(new ClassConstFetch(new Name($class), new Identifier('class'))),
// __FUNCTION__
new MagicConstFunction(),
new Node\Arg(new MagicConstFunction()),
// self::getParamMap(OriginalClass::class, __FUNCTION, func_get_args())
new StaticCall(new Name('self'), 'getParamsMap', [
new ClassConstFetch(new Name($class), new Identifier('class')),
new MagicConstFunction(),
new FuncCall(new Name('func_get_args')),
]),
new Node\Arg(new StaticCall(new Name('self'), 'getParamsMap', [
new Node\Arg(new ClassConstFetch(new Name($class), new Identifier('class'))),
new Node\Arg(new MagicConstFunction()),
new Node\Arg(new FuncCall(new Name('func_get_args'))),
])),
// A closure that wrapped original method code.
new Closure([
new Node\Arg(new Closure([
'params' => value(function () use ($node) {
// Transfer the variadic variable to normal variable at closure argument. ...$params => $parms
$params = $node->getParams();
@ -253,7 +255,7 @@ class ProxyCallVisitor extends NodeVisitorAbstract
new Variable('__method__'),
],
'stmts' => $node->stmts,
]),
])),
]);
$magicConstFunction = new Expression(new Assign(new Variable('__function__'), new Node\Scalar\MagicConst\Function_()));
$magicConstMethod = new Expression(new Assign(new Variable('__method__'), new Node\Scalar\MagicConst\Method()));

View File

@ -35,7 +35,7 @@ class ProxyClassNameVisitor extends NodeVisitorAbstract
{
// Rewirte the class name and extends the original class.
if ($node instanceof Node\Stmt\Class_ && ! $node->isAnonymous()) {
$node->extends = $node->name;
$node->extends = new Node\Name($node->name->name);
$node->name = new Node\Identifier($this->proxyClassName);
return $node;
}

View File

@ -17,7 +17,6 @@ use Hyperf\Di\Definition\ObjectDefinition;
use Hyperf\Di\Exception\NotFoundException;
use Hyperf\Di\Resolver\ResolverDispatcher;
use Hyperf\Dispatcher\Exceptions\InvalidArgumentException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
@ -61,11 +60,12 @@ class Container implements ContainerInterface
{
$this->definitionSource = $definitionSource;
$this->definitionResolver = new ResolverDispatcher($this);
$this->proxyFactory = new ProxyFactory($this);
$this->proxyFactory = new ProxyFactory();
// Auto-register the container.
$this->resolvedEntries = [
self::class => $this,
ContainerInterface::class => $this,
ProxyFactory::class => $this->proxyFactory,
];
}
@ -97,8 +97,6 @@ class Container implements ContainerInterface
* Finds an entry of the container by its identifier and returns it.
*
* @param string $name identifier of the entry to look for
* @throws NotFoundExceptionInterface no entry was found for **this** identifier
* @throws ContainerExceptionInterface error while retrieving the entry
* @return mixed entry
*/
public function get($name)

View File

@ -149,8 +149,7 @@ class DefinitionSource implements DefinitionSourceInterface
}
/**
* @param array|callable|string $definitions
* @param mixed $definition
* @param array|callable|string $definition
*/
private function normalizeDefinition(string $identifier, $definition): ?DefinitionInterface
{

View File

@ -20,7 +20,7 @@ interface DefinitionSourceInterface
* Returns the DI definition for the entry name.
*
* @throws InvalidDefinitionException an invalid definition was found
* @return null|array
* @return null|DefinitionInterface
*/
public function getDefinition(string $name);

View File

@ -20,7 +20,7 @@ class FactoryDefinition implements DefinitionInterface
private $name;
/**
* @var callable
* @var callable|string
*/
private $factory;

View File

@ -13,7 +13,6 @@ declare(strict_types=1);
namespace Hyperf\Di;
use Hyperf\Di\Aop\Ast;
use Hyperf\Di\Definition\FactoryDefinition;
use Hyperf\Di\Definition\ObjectDefinition;
use Hyperf\Utils\Coroutine\Locker as CoLocker;
@ -40,16 +39,11 @@ class ProxyFactory
if (isset(static::$map[$identifier])) {
return static::$map[$identifier];
}
$proxyIdentifier = null;
if ($definition instanceof FactoryDefinition) {
$proxyIdentifier = $definition->getFactory() . '_' . md5($definition->getFactory());
$proxyIdentifier && $definition->setTarget($proxyIdentifier);
$this->loadProxy($definition->getName(), $definition->getFactory());
} elseif ($definition instanceof ObjectDefinition) {
$proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName());
$definition->setProxyClassName($proxyIdentifier);
$this->loadProxy($definition->getClassName(), $definition->getProxyClassName());
}
$proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName());
$definition->setProxyClassName($proxyIdentifier);
$this->loadProxy($definition->getClassName(), $definition->getProxyClassName());
static::$map[$identifier] = $definition;
return static::$map[$identifier];
}

View File

@ -12,7 +12,6 @@ declare(strict_types=1);
namespace Hyperf\Di\Resolver;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionInterface;
use Hyperf\Di\Definition\ObjectDefinition;
use Hyperf\Di\Definition\PropertyInjection;
@ -48,14 +47,12 @@ class ObjectResolver implements ResolverInterface
/**
* ObjectResolver constructor.
*
* @param Container $container
*/
public function __construct(ContainerInterface $container, ResolverInterface $definitionResolver)
{
$this->container = $container;
$this->definitionResolver = $definitionResolver;
$this->proxyFactory = $container->getProxyFactory();
$this->proxyFactory = $container->get(ProxyFactory::class);
$this->parameterResolver = new ParameterResolver($definitionResolver);
}
@ -64,12 +61,18 @@ class ObjectResolver implements ResolverInterface
*
* @param DefinitionInterface $definition object that defines how the value should be obtained
* @param array $parameters optional parameters to use to build the entry
* @throws DependencyException
* @throws InvalidDefinitionException
* @throws DependencyException
* @return mixed value obtained from the definition
*/
public function resolve(DefinitionInterface $definition, array $parameters = [])
{
if (! $definition instanceof ObjectDefinition) {
throw InvalidDefinitionException::create(
$definition,
sprintf('Entry "%s" cannot be resolved: the class is not instanceof ObjectDefinition', $definition->getName())
);
}
return $this->createInstance($definition, $parameters);
}

View File

@ -21,7 +21,7 @@ use ReflectionParameter;
class ParameterResolver
{
/**
* @var DefinitionInterface
* @var ResolverInterface
*/
private $definitionResolver;

View File

@ -23,12 +23,12 @@ use RuntimeException;
class ResolverDispatcher implements ResolverInterface
{
/**
* @var ObjectResolver
* @var null|ObjectResolver
*/
protected $objectResolver;
/**
* @var FactoryResolver
* @var null|FactoryResolver
*/
protected $factoryResolver;

View File

@ -12,11 +12,15 @@ declare(strict_types=1);
namespace HyperfTest\Di;
use Hyperf\Di\Annotation\Aspect as AspectAnnotation;
use Hyperf\Di\Aop\Aspect;
use Hyperf\Di\Aop\RewriteCollection;
use HyperfTest\Di\Stub\AnnotationCollector;
use HyperfTest\Di\Stub\AspectCollector;
use HyperfTest\Di\Stub\DemoAnnotation;
use HyperfTest\Di\Stub\Foo;
use HyperfTest\Di\Stub\Foo2Aspect;
use HyperfTest\Di\Stub\FooAspect;
use PHPUnit\Framework\TestCase;
/**
@ -203,4 +207,22 @@ class AopAspectTest extends TestCase
$this->assertFalse(Aspect::isMatch('Foo/Bar/Baz', 'method', $rule));
$this->assertFalse(Aspect::isMatch('Foo/Bar', 'test', $rule));
}
public function testAspectAnnotation()
{
$annotation = new AspectAnnotation();
$annotation->collectClass(FooAspect::class);
$annotation->collectClass(Foo2Aspect::class);
$this->assertSame([
'classes' => [Foo::class],
'annotations' => [DemoAnnotation::class],
], AspectCollector::getRule(FooAspect::class));
$this->assertSame([
'classes' => [Foo::class],
'annotations' => [],
], AspectCollector::getRule(Foo2Aspect::class));
}
}

125
src/di/tests/AstTest.php Normal file
View File

@ -0,0 +1,125 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di;
use Hyperf\Di\Aop\Ast;
use HyperfTest\Di\Stub\AspectCollector;
use HyperfTest\Di\Stub\Ast\Bar2;
use HyperfTest\Di\Stub\Ast\Bar3;
use HyperfTest\Di\Stub\Ast\BarAspect;
use HyperfTest\Di\Stub\Ast\Foo;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class AstTest extends TestCase
{
public function testProxy()
{
$ast = new Ast();
$proxyClass = Foo::class . 'Proxy';
$code = $ast->proxy(Foo::class, $proxyClass);
$this->assertEquals('<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class FooProxy extends Foo
{
use \Hyperf\Di\Aop\ProxyTrait;
}', $code);
}
public function testParentMethods()
{
$ast = new Ast();
$proxyClass = Bar2::class . 'Proxy';
$code = $ast->proxy(Bar2::class, $proxyClass);
$this->assertEquals('<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Bar2Proxy extends Bar2
{
use \Hyperf\Di\Aop\ProxyTrait;
public function __construct(int $id)
{
Bar::__construct($id);
}
public static function build()
{
return Bar::$items;
}
}', $code);
}
public function testRewriteMethods()
{
$aspect = BarAspect::class;
AspectCollector::setAround($aspect, [
Bar3::class,
], []);
$ast = new Ast();
$proxyClass = Bar3::class . 'Proxy';
$code = $ast->proxy(Bar3::class, $proxyClass);
$this->assertEquals('<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Bar3Proxy extends Bar3
{
use \Hyperf\Di\Aop\ProxyTrait;
public function getId() : int
{
$__function__ = __FUNCTION__;
$__method__ = __METHOD__;
return self::__proxyCall(Bar3::class, __FUNCTION__, self::getParamsMap(Bar3::class, __FUNCTION__, func_get_args()), function () use($__function__, $__method__) {
return parent::getId();
});
}
}', $code);
}
}

View File

@ -15,6 +15,8 @@ namespace HyperfTest\Di;
use Hyperf\Di\Annotation\Scanner;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSource;
use HyperfTest\Di\Stub\Foo;
use HyperfTest\Di\Stub\FooFactory;
use PHPUnit\Framework\TestCase;
/**
@ -31,4 +33,13 @@ class DefinitionSourceTest extends TestCase
});
$this->assertEquals('bar', $container->get('Foo'));
}
public function testDefinitionFactory()
{
$container = new Container(new DefinitionSource([], [], new Scanner()));
$container->getDefinitionSource()->addDefinition('Foo', FooFactory::class);
$foo = $container->get('Foo');
$this->assertInstanceOf(Foo::class, $foo);
}
}

View File

@ -0,0 +1,46 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Bar
{
public $id = 0;
public static $items = [];
public function __construct(int $id)
{
$this->id = $id;
}
public function getId(): int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public static function make()
{
return new static(0);
}
public static function getItems()
{
return static::items;
}
}

View File

@ -0,0 +1,26 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Bar2 extends Bar
{
public function __construct(int $id)
{
parent::__construct($id);
}
public static function build()
{
return parent::$items;
}
}

View File

@ -0,0 +1,21 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Bar3 extends Bar
{
public function getId(): int
{
return parent::getId();
}
}

View File

@ -0,0 +1,28 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
class BarAspect extends AbstractAspect
{
public $classes = [
Bar3::class,
];
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
return $proceedingJoinPoint->process();
}
}

View File

@ -0,0 +1,17 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub\Ast;
class Foo
{
}

View File

@ -0,0 +1,36 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
/**
* @Aspect
*/
class Foo2Aspect implements AroundInterface
{
/**
* The classes that you want to weaving.
*
* @var array
*/
public $classes = [
Foo::class,
];
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
}
}

View File

@ -0,0 +1,45 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AroundInterface;
use Hyperf\Di\Aop\ProceedingJoinPoint;
/**
* @Aspect
*/
class FooAspect implements AroundInterface
{
/**
* The classes that you want to weaving.
*
* @var array
*/
public $classes = [
Foo::class,
];
/**
* The annotations that you want to weaving.
*
* @var array
*/
public $annotations = [
DemoAnnotation::class,
];
public function process(ProceedingJoinPoint $proceedingJoinPoint)
{
}
}

View File

@ -0,0 +1,21 @@
<?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-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Di\Stub;
class FooFactory
{
public function __invoke()
{
return new Foo();
}
}