Skip to content

Commit ea48c04

Browse files
committed
Support any type for container variables and factory functions
1 parent 21512e9 commit ea48c04

3 files changed

Lines changed: 43 additions & 72 deletions

File tree

docs/best-practices/controllers.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,10 @@ $app = new FrameworkX\App($container);
281281
```
282282

283283
Factory functions used in the container configuration map may also reference
284-
variables defined in the container configuration. You may use any object or
285-
scalar or `array` or `null` value for container variables or factory functions
286-
that return any such value. This can be particularly useful when combining
287-
autowiring with some manual configuration like this:
284+
variables defined in the container configuration. You may use a value of any
285+
type for container variables or factory functions that return any such value.
286+
This can be particularly useful when combining autowiring with some manual
287+
configuration like this:
288288

289289
=== "Scalar values"
290290

src/Container.php

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
*/
1515
class Container
1616
{
17-
/** @var array<string,object|callable():(object|scalar|array<mixed>|null)|scalar|array<mixed>|null>|ContainerInterface */
17+
/** @var array<string,mixed>|ContainerInterface */
1818
private $container;
1919

2020
/** @var bool */
2121
private $useProcessEnv;
2222

2323
/**
24-
* @param array<string,callable():(object|scalar|array<mixed>|null) | object | scalar | array<mixed> | null>|ContainerInterface $config
24+
* @param array<string,mixed>|ContainerInterface $config
2525
* @throws \TypeError if given $config is invalid
2626
*/
2727
public function __construct($config = [])
@@ -39,11 +39,6 @@ public function __construct($config = [])
3939
'Argument #1 ($config) for key "' . $name . '" must be of type ' . $name . '|Closure|string, ' . $this->gettype($value) . ' given'
4040
);
4141
}
42-
if (!\is_object($value) && !\is_scalar($value) && !\is_array($value) && $value !== null) {
43-
throw new \TypeError(
44-
'Argument #1 ($config) for key "' . $name . '" must be of type object|string|int|float|bool|array|null|Closure, ' . $this->gettype($value) . ' given'
45-
);
46-
}
4742
}
4843
$this->container = $config;
4944

@@ -385,12 +380,12 @@ private function hasVariable(string $name): bool
385380
}
386381

387382
/**
388-
* @return object|string|int|float|bool|array<mixed>|null
383+
* @return mixed
389384
* @throws \TypeError if container factory returns an unexpected type
390385
* @throws \Error if $name can not be loaded
391386
* @throws \Throwable if container factory function throws unexpected exception
392387
*/
393-
private function loadVariable(string $name, int $depth = 64) /*: object|string|int|float|bool|array|null (PHP 8.0+) */
388+
private function loadVariable(string $name, int $depth = 64) /*: mixed (PHP 8.0+) */
394389
{
395390
\assert($this->hasVariable($name));
396391
\assert(\is_array($this->container) || !$this->container->has($name));
@@ -409,11 +404,7 @@ private function loadVariable(string $name, int $depth = 64) /*: object|string|i
409404
$this->container[$name] = $factory;
410405
}
411406

412-
if (!\is_object($value) && !\is_scalar($value) && !\is_array($value) && $value !== null) {
413-
throw new \TypeError(
414-
'Return value of ' . self::functionName($closure) . ' for $' . $name . ' must be of type object|string|int|float|bool|array|null, ' . $this->gettype($value) . ' returned'
415-
);
416-
} elseif ($value instanceof \Closure) {
407+
if ($value instanceof \Closure) {
417408
throw new \TypeError(
418409
'Return value of ' . self::functionName($closure) . ' for $' . $name . ' must not be of type Closure'
419410
);
@@ -433,12 +424,11 @@ private function loadVariable(string $name, int $depth = 64) /*: object|string|i
433424
\assert($this->useProcessEnv && $value !== false);
434425
}
435426

436-
\assert(\is_object($value) || \is_scalar($value) || \is_array($value) || $value === null);
437427
return $value;
438428
}
439429

440430
/**
441-
* @param object|string|int|float|bool|array<mixed>|null $value
431+
* @param mixed $value
442432
* @param \ReflectionType $type
443433
* @throws void
444434
*/

tests/ContainerTest.php

Lines changed: 33 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,34 +1348,6 @@ public function __construct(string $stdClass)
13481348
$callable($request);
13491349
}
13501350

1351-
public function testCallableReturnsCallableThatThrowsWhenFactoryReferencesVariableMappedFromFactoryWithUnexpectedReturnType(): void
1352-
{
1353-
$request = new ServerRequest('GET', 'http://example.com/');
1354-
1355-
$controller = new class(new \stdClass()) {
1356-
public function __construct(\stdClass $data)
1357-
{
1358-
assert($data instanceof \stdClass);
1359-
}
1360-
};
1361-
1362-
$line = __LINE__ + 5;
1363-
$container = new Container([
1364-
\stdClass::class => function (string $http) {
1365-
return (object) ['name' => $http];
1366-
},
1367-
'http' => function () {
1368-
return tmpfile();
1369-
}
1370-
]);
1371-
1372-
$callable = $container->callable(get_class($controller));
1373-
1374-
$this->expectException(\Error::class);
1375-
$this->expectExceptionMessage('Return value of {closure:' . __FILE__ . ':' . $line . '}() for $http must be of type object|string|int|float|bool|array|null, resource returned');
1376-
$callable($request);
1377-
}
1378-
13791351
public function testCallableReturnsCallableThatThrowsWhenFactoryReferencesObjectVariableMappedFromFactoryWithReturnsUnexpectedInteger(): void
13801352
{
13811353
$request = new ServerRequest('GET', 'http://example.com/');
@@ -1660,16 +1632,6 @@ public function __construct(string $name)
16601632
$callable($request);
16611633
}
16621634

1663-
public function testCtorThrowsWhenConfigContainsInvalidResource(): void
1664-
{
1665-
$this->expectException(\TypeError::class);
1666-
$this->expectExceptionMessage('Argument #1 ($config) for key "file" must be of type object|string|int|float|bool|array|null|Closure, resource given');
1667-
1668-
new Container([ // @phpstan-ignore-line
1669-
'file' => tmpfile()
1670-
]);
1671-
}
1672-
16731635
public function testCtorThrowsWhenConfigForClassContainsInvalidObject(): void
16741636
{
16751637
$this->expectException(\TypeError::class);
@@ -2019,6 +1981,26 @@ public function testGetEnvReturnsStringFromFactoryFunctionWithFalseType(): void
20191981
$this->assertEquals('false', $container->getEnv('X_FOO'));
20201982
}
20211983

1984+
public function testGetEnvReturnsStringFromFactoryFunctionWithResourceValue(): void
1985+
{
1986+
$container = new Container([
1987+
'X_FOO' => function ($stream) { return get_resource_type($stream); },
1988+
'stream' => tmpfile()
1989+
]);
1990+
1991+
$this->assertEquals('stream', $container->getEnv('X_FOO'));
1992+
}
1993+
1994+
public function testGetEnvReturnsStringFromFactoryFunctionWithResourceValueFromFactoryFunction(): void
1995+
{
1996+
$container = new Container([
1997+
'X_FOO' => function ($stream) { return get_resource_type($stream); },
1998+
'stream' => function () { return tmpfile(); }
1999+
]);
2000+
2001+
$this->assertEquals('stream', $container->getEnv('X_FOO'));
2002+
}
2003+
20222004
/**
20232005
* @requires PHP 8
20242006
*/
@@ -2347,20 +2329,6 @@ public function testGetEnvThrowsIfFactoryFunctionThrows(): void
23472329
$container->getEnv('X_FOO');
23482330
}
23492331

2350-
public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidResource(): void
2351-
{
2352-
$line = __LINE__ + 2;
2353-
$container = new Container([
2354-
'X_FOO' => function () {
2355-
return tmpfile();
2356-
}
2357-
]);
2358-
2359-
$this->expectException(\TypeError::class);
2360-
$this->expectExceptionMessage('Return value of {closure:' . __FILE__ . ':' . $line . '}() for $X_FOO must be of type object|string|int|float|bool|array|null, resource returned');
2361-
$container->getEnv('X_FOO');
2362-
}
2363-
23642332
public function testGetEnvThrowsIfFactoryFunctionReturnsInvalidClosure(): void
23652333
{
23662334
$line = __LINE__ + 2;
@@ -2623,6 +2591,19 @@ public function testGetEnvThrowsWhenFactoryFunctionExpectsObjectTypeButWrongType
26232591
$container->getEnv('X_FOO');
26242592
}
26252593

2594+
public function testGetEnvThrowsWhenFactoryFunctionExpectsStringTypeButResourceGiven(): void
2595+
{
2596+
$line = __LINE__ + 2;
2597+
$container = new Container([
2598+
'X_FOO' => function (string $data) { return $data; },
2599+
'data' => tmpfile()
2600+
]);
2601+
2602+
$this->expectException(\TypeError::class);
2603+
$this->expectExceptionMessage('Argument #1 ($data) of {closure:' . __FILE__ . ':' . $line . '}() for $X_FOO must be of type string, resource given');
2604+
$container->getEnv('X_FOO');
2605+
}
2606+
26262607
public function testGetEnvThrowsWhenFactoryFunctionExpectsArrayTypeButWrongTypeGiven(): void
26272608
{
26282609
$line = __LINE__ + 2;

0 commit comments

Comments
 (0)