Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/best-practices/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Comment thread
clue marked this conversation as resolved.

=== "Scalar values"

Expand Down
22 changes: 6 additions & 16 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
*/
class Container
{
/** @var array<string,object|callable():(object|scalar|array<mixed>|null)|scalar|array<mixed>|null>|ContainerInterface */
/** @var array<string,mixed>|ContainerInterface */
private $container;

/** @var bool */
private $useProcessEnv;

/**
* @param array<string,callable():(object|scalar|array<mixed>|null) | object | scalar | array<mixed> | null>|ContainerInterface $config
* @param array<string,mixed>|ContainerInterface $config
* @throws \TypeError if given $config is invalid
*/
public function __construct($config = [])
Expand All @@ -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;

Expand Down Expand Up @@ -385,12 +380,12 @@ private function hasVariable(string $name): bool
}

/**
* @return object|string|int|float|bool|array<mixed>|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));
Expand All @@ -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'
);
Expand All @@ -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<mixed>|null $value
* @param mixed $value
* @param \ReflectionType $type
* @throws void
*/
Expand Down
85 changes: 33 additions & 52 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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/');
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down