diff --git a/src/Container.php b/src/Container.php index 4063b05..ff2f342 100644 --- a/src/Container.php +++ b/src/Container.php @@ -412,13 +412,8 @@ private function loadVariable(string $name, int $depth = 64) /*: mixed (PHP 8.0+ $this->container[$name] = $factory; } - if ($value instanceof \Closure) { - throw new \TypeError( - 'Return value of ' . self::functionName($closure) . ' for $' . $name . ' must not be of type Closure' - ); - } - - $this->container[$name] = $value; + // cache result, but wrap closure so it is not invoked as factory on next call + $this->container[$name] = $value instanceof \Closure ? static function () use ($value) { return $value; } : $value; } elseif (\is_array($this->container) && \array_key_exists($name, $this->container)) { $value = $this->container[$name]; } elseif (isset($_ENV[$name])) { diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php index 96809e6..6cebb6e 100644 --- a/tests/ContainerTest.php +++ b/tests/ContainerTest.php @@ -1936,6 +1936,23 @@ public function testGetEnvReturnsStringFromFactoryFunctionWithCallableTypeAndArr $this->assertEquals('UTC', $container->getEnv('X_FOO')); } + public function testGetEnvReturnsStringFromFactoryFunctionWithClosureValueFromFactoryFunctionCalledOnlyOnce(): void + { + $calls = 0; + $closure = function () { }; + $container = new Container([ + 'X_FOO' => function (\Closure $fn) use ($closure) { return var_export($fn === $closure, true); }, + 'X_BAR' => function (\Closure $fn) use ($closure) { return var_export($fn === $closure, true); }, + 'X_BAZ' => function (\Closure $fn) use ($closure) { return var_export($fn === $closure, true); }, + 'fn' => function () use ($closure, &$calls) { ++$calls; return $closure; } + ]); + + $this->assertEquals('true', $container->getEnv('X_FOO')); + $this->assertEquals('true', $container->getEnv('X_BAR')); + $this->assertEquals('true', $container->getEnv('X_BAZ')); + $this->assertEquals(1, $calls); + } + /** * @requires PHP 8.2 */ @@ -2316,7 +2333,6 @@ public function testGetEnvThrowsIfFactoryFunctionThrows(): void public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidClosure(): void { - $line = __LINE__ + 2; $container = new Container([ 'X_FOO' => function () { return function () { @@ -2326,7 +2342,7 @@ public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidClosure(): void ]); $this->expectException(\TypeError::class); - $this->expectExceptionMessage('Return value of {closure:' . __FILE__ . ':' . $line . '}() for $X_FOO must not be of type Closure'); + $this->expectExceptionMessage('Return value of FrameworkX\Container::getEnv() for $X_FOO must be of type string|null, Closure returned'); $container->getEnv('X_FOO'); }