diff --git a/docs/best-practices/controllers.md b/docs/best-practices/controllers.md index b1cd489..0a95176 100644 --- a/docs/best-practices/controllers.md +++ b/docs/best-practices/controllers.md @@ -281,10 +281,10 @@ $app = new FrameworkX\App($container); ``` Factory functions used in the container configuration map may also reference -variables defined in the container configuration. You may use any object or -scalar or `array` or `null` value for container variables or factory functions -that return any such value. This can be particularly useful when combining -autowiring with some manual configuration like this: +variables defined in the container configuration. You may use a value of any +type for container variables or factory functions that return any such value. +This can be particularly useful when combining autowiring with some manual +configuration like this: === "Scalar values" diff --git a/src/Container.php b/src/Container.php index 10dcc70..14e613c 100644 --- a/src/Container.php +++ b/src/Container.php @@ -14,14 +14,14 @@ */ class Container { - /** @var array|null)|scalar|array|null>|ContainerInterface */ + /** @var array|ContainerInterface */ private $container; /** @var bool */ private $useProcessEnv; /** - * @param array|null) | object | scalar | array | null>|ContainerInterface $config + * @param array|ContainerInterface $config * @throws \TypeError if given $config is invalid */ public function __construct($config = []) @@ -39,11 +39,6 @@ public function __construct($config = []) 'Argument #1 ($config) for key "' . $name . '" must be of type ' . $name . '|Closure|string, ' . $this->gettype($value) . ' given' ); } - if (!\is_object($value) && !\is_scalar($value) && !\is_array($value) && $value !== null) { - throw new \TypeError( - 'Argument #1 ($config) for key "' . $name . '" must be of type object|string|int|float|bool|array|null|Closure, ' . $this->gettype($value) . ' given' - ); - } } $this->container = $config; @@ -385,12 +380,12 @@ private function hasVariable(string $name): bool } /** - * @return object|string|int|float|bool|array|null + * @return mixed * @throws \TypeError if container factory returns an unexpected type * @throws \Error if $name can not be loaded * @throws \Throwable if container factory function throws unexpected exception */ - private function loadVariable(string $name, int $depth = 64) /*: object|string|int|float|bool|array|null (PHP 8.0+) */ + private function loadVariable(string $name, int $depth = 64) /*: mixed (PHP 8.0+) */ { \assert($this->hasVariable($name)); \assert(\is_array($this->container) || !$this->container->has($name)); @@ -409,11 +404,7 @@ private function loadVariable(string $name, int $depth = 64) /*: object|string|i $this->container[$name] = $factory; } - if (!\is_object($value) && !\is_scalar($value) && !\is_array($value) && $value !== null) { - throw new \TypeError( - 'Return value of ' . self::functionName($closure) . ' for $' . $name . ' must be of type object|string|int|float|bool|array|null, ' . $this->gettype($value) . ' returned' - ); - } elseif ($value instanceof \Closure) { + if ($value instanceof \Closure) { throw new \TypeError( 'Return value of ' . self::functionName($closure) . ' for $' . $name . ' must not be of type Closure' ); @@ -433,12 +424,11 @@ private function loadVariable(string $name, int $depth = 64) /*: object|string|i \assert($this->useProcessEnv && $value !== false); } - \assert(\is_object($value) || \is_scalar($value) || \is_array($value) || $value === null); return $value; } /** - * @param object|string|int|float|bool|array|null $value + * @param mixed $value * @param \ReflectionType $type * @throws void */ diff --git a/tests/ContainerTest.php b/tests/ContainerTest.php index 7b57287..a172b36 100644 --- a/tests/ContainerTest.php +++ b/tests/ContainerTest.php @@ -1348,34 +1348,6 @@ public function __construct(string $stdClass) $callable($request); } - public function testCallableReturnsCallableThatThrowsWhenFactoryReferencesVariableMappedFromFactoryWithUnexpectedReturnType(): void - { - $request = new ServerRequest('GET', 'http://example.com/'); - - $controller = new class(new \stdClass()) { - public function __construct(\stdClass $data) - { - assert($data instanceof \stdClass); - } - }; - - $line = __LINE__ + 5; - $container = new Container([ - \stdClass::class => function (string $http) { - return (object) ['name' => $http]; - }, - 'http' => function () { - return tmpfile(); - } - ]); - - $callable = $container->callable(get_class($controller)); - - $this->expectException(\Error::class); - $this->expectExceptionMessage('Return value of {closure:' . __FILE__ . ':' . $line . '}() for $http must be of type object|string|int|float|bool|array|null, resource returned'); - $callable($request); - } - public function testCallableReturnsCallableThatThrowsWhenFactoryReferencesObjectVariableMappedFromFactoryWithReturnsUnexpectedInteger(): void { $request = new ServerRequest('GET', 'http://example.com/'); @@ -1660,16 +1632,6 @@ public function __construct(string $name) $callable($request); } - public function testCtorThrowsWhenConfigContainsInvalidResource(): void - { - $this->expectException(\TypeError::class); - $this->expectExceptionMessage('Argument #1 ($config) for key "file" must be of type object|string|int|float|bool|array|null|Closure, resource given'); - - new Container([ // @phpstan-ignore-line - 'file' => tmpfile() - ]); - } - public function testCtorThrowsWhenConfigForClassContainsInvalidObject(): void { $this->expectException(\TypeError::class); @@ -2019,6 +1981,26 @@ public function testGetEnvReturnsStringFromFactoryFunctionWithFalseType(): void $this->assertEquals('false', $container->getEnv('X_FOO')); } + public function testGetEnvReturnsStringFromFactoryFunctionWithResourceValue(): void + { + $container = new Container([ + 'X_FOO' => function ($stream) { return get_resource_type($stream); }, + 'stream' => tmpfile() + ]); + + $this->assertEquals('stream', $container->getEnv('X_FOO')); + } + + public function testGetEnvReturnsStringFromFactoryFunctionWithResourceValueFromFactoryFunction(): void + { + $container = new Container([ + 'X_FOO' => function ($stream) { return get_resource_type($stream); }, + 'stream' => function () { return tmpfile(); } + ]); + + $this->assertEquals('stream', $container->getEnv('X_FOO')); + } + /** * @requires PHP 8 */ @@ -2347,20 +2329,6 @@ public function testGetEnvThrowsIfFactoryFunctionThrows(): void $container->getEnv('X_FOO'); } - public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidResource(): void - { - $line = __LINE__ + 2; - $container = new Container([ - 'X_FOO' => function () { - return tmpfile(); - } - ]); - - $this->expectException(\TypeError::class); - $this->expectExceptionMessage('Return value of {closure:' . __FILE__ . ':' . $line . '}() for $X_FOO must be of type object|string|int|float|bool|array|null, resource returned'); - $container->getEnv('X_FOO'); - } - public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidClosure(): void { $line = __LINE__ + 2; @@ -2623,6 +2591,19 @@ public function testGetEnvThrowsWhenFactoryFunctionExpectsObjectTypeButWrongType $container->getEnv('X_FOO'); } + public function testGetEnvThrowsWhenFactoryFunctionExpectsStringTypeButResourceGiven(): void + { + $line = __LINE__ + 2; + $container = new Container([ + 'X_FOO' => function (string $data) { return $data; }, + 'data' => tmpfile() + ]); + + $this->expectException(\TypeError::class); + $this->expectExceptionMessage('Argument #1 ($data) of {closure:' . __FILE__ . ':' . $line . '}() for $X_FOO must be of type string, resource given'); + $container->getEnv('X_FOO'); + } + public function testGetEnvThrowsWhenFactoryFunctionExpectsArrayTypeButWrongTypeGiven(): void { $line = __LINE__ + 2;