Skip to content

Commit 28ef07d

Browse files
committed
Allow recursive container calls from factory functions for classes
1 parent 9af8af8 commit 28ef07d

2 files changed

Lines changed: 89 additions & 27 deletions

File tree

src/Container.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,17 @@ private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+)
245245

246246
$this->container[$name] = $value;
247247
} elseif ($this->container[$name] instanceof \Closure) {
248-
// build list of factory parameters based on parameter types
249-
$closure = new \ReflectionFunction($this->container[$name]);
250-
$params = $this->loadFunctionParams($closure, $depth, true, \explode("\0", $name)[0]);
248+
$factory = $this->container[$name];
249+
$closure = new \ReflectionFunction($factory);
251250

252251
// invoke factory with list of parameters
253-
$value = $params === [] ? ($this->container[$name])() : ($this->container[$name])(...$params);
252+
// temporarily unset factory reference to allow loading recursive variables from environment
253+
try {
254+
unset($this->container[$name]);
255+
$value = $factory(...$this->loadFunctionParams($closure, $depth, true, \explode("\0", $name)[0]));
256+
} finally {
257+
$this->container[$name] = $factory;
258+
}
254259

255260
if (\is_string($value)) {
256261
if ($depth < 1) {

tests/ContainerTest.php

Lines changed: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,7 +1305,7 @@ public function __construct(\stdClass $data)
13051305
$callable = $container->callable(get_class($controller));
13061306

13071307
$this->expectException(\Error::class);
1308-
$this->expectExceptionMessage('Argument #1 ($stdClass) of {closure:' . __FILE__ . ':' . $line .'}() for $stdClass requires container config with type string, none given');
1308+
$this->expectExceptionMessage('Argument #1 ($stdClass) of {closure:' . __FILE__ . ':' . $line .'}() for stdClass requires container config with type string, none given');
13091309
$callable($request);
13101310
}
13111311

@@ -1820,22 +1820,6 @@ public function testCallableReturnsCallableThatThrowsWhenFactoryRequiresUndefine
18201820
$callable($request);
18211821
}
18221822

1823-
public function testCallableReturnsCallableThatThrowsWhenFactoryRequiresRecursiveClass(): void
1824-
{
1825-
$request = new ServerRequest('GET', 'http://example.com/');
1826-
1827-
$line = __LINE__ + 2;
1828-
$container = new Container([
1829-
\stdClass::class => function (\stdClass $data) { return $data; }
1830-
]);
1831-
1832-
$callable = $container->callable(\stdClass::class);
1833-
1834-
$this->expectException(\Error::class);
1835-
$this->expectExceptionMessage('Argument #1 ($data) of {closure:' . __FILE__ . ':' . $line .'}() for stdClass is recursive');
1836-
$callable($request);
1837-
}
1838-
18391823
public function testCallableReturnsCallableThatThrowsWhenFactoryIsRecursive(): void
18401824
{
18411825
$request = new ServerRequest('GET', 'http://example.com/');
@@ -2614,6 +2598,45 @@ public function testGetObjectReturnsAccessLogHandlerInstanceFromConfig(): void
26142598
$this->assertSame($accessLogHandler, $ret);
26152599
}
26162600

2601+
public function testGetObjectReturnsAccessLogHandlerInstanceFromFactoryFunction(): void
2602+
{
2603+
$accessLogHandler = new AccessLogHandler();
2604+
2605+
$container = new Container([
2606+
AccessLogHandler::class => function () use ($accessLogHandler) {
2607+
return $accessLogHandler;
2608+
}
2609+
]);
2610+
2611+
$ret = $container->getObject(AccessLogHandler::class);
2612+
2613+
$this->assertSame($accessLogHandler, $ret);
2614+
}
2615+
2616+
public function testGetObjectReturnDefaultStdclassInstanceWhenFactoryFunctionHasRecursiveArgument(): void
2617+
{
2618+
$container = new Container([
2619+
\stdClass::class => function (\stdClass $object) { return $object; }
2620+
]);
2621+
2622+
$ret = $container->getObject(\stdClass::class);
2623+
2624+
$this->assertInstanceOf(\stdClass::class, $ret);
2625+
}
2626+
2627+
public function testGetObjectReturnDefaultStdclassInstanceWhenFactoryFunctionUsesRecursiveGetObject(): void
2628+
{
2629+
$container = new Container([
2630+
\stdClass::class => function (Container $container) {
2631+
return $container->getObject(\stdClass::class);
2632+
}
2633+
]);
2634+
2635+
$ret = $container->getObject(\stdClass::class);
2636+
2637+
$this->assertInstanceOf(\stdClass::class, $ret);
2638+
}
2639+
26172640
public function testGetObjectReturnsSelfContainerByDefault(): void
26182641
{
26192642
$container = new Container([]);
@@ -2623,6 +2646,32 @@ public function testGetObjectReturnsSelfContainerByDefault(): void
26232646
$this->assertSame($container, $ret);
26242647
}
26252648

2649+
public function testGetObjectReturnsSelfContainerIfFactoryFunctionHasRecursiveContainerArgument(): void
2650+
{
2651+
$container = new Container([
2652+
Container::class => function (Container $container): Container {
2653+
return $container;
2654+
}
2655+
]);
2656+
2657+
$ret = $container->getObject(Container::class);
2658+
2659+
$this->assertSame($container, $ret);
2660+
}
2661+
2662+
public function testGetObjectReturnsSelfContainerIfFactoryFunctionUsesRecursiveGetObject(): void
2663+
{
2664+
$container = new Container([
2665+
Container::class => function (Container $container): Container {
2666+
return $container->getObject(Container::class);
2667+
}
2668+
]);
2669+
2670+
$ret = $container->getObject(Container::class);
2671+
2672+
$this->assertSame($container, $ret);
2673+
}
2674+
26262675
public function testGetObjectReturnsOtherContainerFromConfig(): void
26272676
{
26282677
$other = new Container();
@@ -2785,18 +2834,26 @@ public function testGetObjectThrowsIfFactoryFunctionIsRecursive(): void
27852834
$container->getObject(AccessLogHandler::class);
27862835
}
27872836

2788-
public function testGetObjectThrowsIfFactoryFunctionHasRecursiveContainerArgument(): void
2837+
public function testGetObjectThrowsIfConstructorRequiresContainerConfig(): void
2838+
{
2839+
$container = new Container([]);
2840+
2841+
$this->expectException(\Error::class);
2842+
$this->expectExceptionMessage('Argument #1 ($path) of ' . LogStreamHandler::class . '::__construct() requires container config with type string, none given');
2843+
$container->getObject(LogStreamHandler::class);
2844+
}
2845+
2846+
public function testGetObjectThrowsIfFactoryFunctionHasClassArgumentWithConstructorThatRequiresContainerConfig(): void
27892847
{
2790-
$line = __LINE__ + 2;
27912848
$container = new Container([
2792-
Container::class => function (Container $container): Container {
2793-
return $container;
2849+
AccessLogHandler::class => function (LogStreamHandler $log) {
2850+
return new AccessLogHandler();
27942851
}
27952852
]);
27962853

27972854
$this->expectException(\Error::class);
2798-
$this->expectExceptionMessage('Argument #1 ($container) of {closure:' . __FILE__ . ':' . $line .'}() for FrameworkX\Container is recursive');
2799-
$container->getObject(Container::class);
2855+
$this->expectExceptionMessage('Argument #1 ($path) of ' . LogStreamHandler::class . '::__construct() requires container config with type string, none given');
2856+
$container->getObject(AccessLogHandler::class);
28002857
}
28012858

28022859
public function testGetObjectThrowsIfConfigReferencesInterface(): void

0 commit comments

Comments
 (0)