Skip to content

Commit

Permalink
Support scalar container variables for factory functions
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Jul 29, 2022
1 parent 350cd67 commit b63f5f7
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 39 deletions.
14 changes: 7 additions & 7 deletions docs/best-practices/controllers.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,8 @@ $container = new FrameworkX\Container([
```

Factory functions used in the container configuration map may also reference
string variables defined in the container configuration. You may also use
factory functions that return string variables. This can be particularly useful
scalar variables defined in the container configuration. You may also use
factory functions that return scalar variables. This can be particularly useful
when combining autowiring with some manual configuration like this:

```php title="public/index.php"
Expand All @@ -295,11 +295,11 @@ when combining autowiring with some manual configuration like this:
require __DIR__ . '/../vendor/autoload.php';

$container = new FrameworkX\Container([
Acme\Todo\UserController::class => function (string $name, string $hostname) {
// example UserController class requires two string arguments
return new Acme\Todo\UserController($name, $hostname);
Acme\Todo\UserController::class => function (bool $debug, string $hostname) {
// example UserController class requires two scalar arguments
return new Acme\Todo\UserController($debug, $hostname);
},
'name' => 'Acme',
'debug' => false,
'hostname' => fn(): string => gethostname()
]);

Expand All @@ -308,7 +308,7 @@ $container = new FrameworkX\Container([

> ℹ️ **Avoiding name conflicts**
>
> Note that class names and string variables share the same container
> Note that class names and scalar variables share the same container
> configuration map and as such might be subject to name collisions as a single
> entry may only have a single value. For this reason, container variables will
> only be used for container functions by default. We highly recommend using
Expand Down
35 changes: 22 additions & 13 deletions src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
*/
class Container
{
/** @var array<string,object|callable():(object|string)|string>|ContainerInterface */
/** @var array<string,object|callable():(object|scalar)|scalar>|ContainerInterface */
private $container;

/** @var array<string,callable():(object|string) | object | string>|ContainerInterface $loader */
/** @var array<string,callable():(object|scalar) | object | scalar>|ContainerInterface $loader */
public function __construct($loader = [])
{
if (!\is_array($loader) && !$loader instanceof ContainerInterface) {
Expand All @@ -23,7 +23,7 @@ public function __construct($loader = [])
}

foreach (($loader instanceof ContainerInterface ? [] : $loader) as $name => $value) {
if (!\is_string($value) && !$value instanceof \Closure && !$value instanceof $name) {
if (!\is_scalar($value) && !$value instanceof \Closure && !$value instanceof $name) {
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . (is_object($value) ? get_class($value) : gettype($value)));
}
}
Expand Down Expand Up @@ -154,6 +154,8 @@ private function loadObject(string $name, int $depth = 64) /*: object (PHP 7.2+)
}

$this->container[$name] = $value;
} elseif (\is_scalar($this->container[$name])) {
throw new \BadMethodCallException('Map for ' . $name . ' contains unexpected ' . \gettype($this->container[$name]));
}

assert($this->container[$name] instanceof $name);
Expand Down Expand Up @@ -219,13 +221,13 @@ private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $

assert($type instanceof \ReflectionNamedType);

// load string variables from container
if ($allowVariables && $type->getName() === 'string') {
$params[] = $this->loadVariable($parameter->getName(), $depth);
// load variables from container for primitive/scalar types
if ($allowVariables && \in_array($type->getName(), ['string', 'int', 'float', 'bool'])) {
$params[] = $this->loadVariable($parameter->getName(), $type->getName(), $depth);
continue;
}

// abort for other primitive types
// abort for other primitive types (array etc.)
if ($type->isBuiltin()) {
throw new \BadMethodCallException(self::parameterError($parameter) . ' expects unsupported type ' . $type->getName());
}
Expand All @@ -241,8 +243,11 @@ private function loadFunctionParams(\ReflectionFunctionAbstract $function, int $
return $params;
}

/** @throws \BadMethodCallException if $name is not a valid string variable */
private function loadVariable(string $name, int $depth): string
/**
* @return string|int|float|bool
* @throws \BadMethodCallException if $name is not a valid scalar variable
*/
private function loadVariable(string $name, string $type, int $depth) /*: string|int|float|bool (PHP 8.0+) */
{
if (!isset($this->container[$name])) {
throw new \BadMethodCallException('Container variable $' . $name . ' is not defined');
Expand All @@ -260,16 +265,20 @@ private function loadVariable(string $name, int $depth): string
// invoke factory with list of parameters
$value = $params === [] ? ($this->container[$name])() : ($this->container[$name])(...$params);

if (!\is_string($value)) {
throw new \BadMethodCallException('Container variable $' . $name . ' expected type string from factory, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
if (!\is_scalar($value)) {
throw new \BadMethodCallException('Container variable $' . $name . ' expected scalar type from factory, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
}

$this->container[$name] = $value;
}

$value = $this->container[$name];
if (!\is_string($value)) {
throw new \BadMethodCallException('Container variable $' . $name . ' expected type string, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
if (!\is_scalar($value)) {
throw new \BadMethodCallException('Container variable $' . $name . ' expected scalar type, but got ' . (\is_object($value) ? \get_class($value) : \gettype($value)));
}

if (($type === 'string' && !\is_string($value)) || ($type === 'int' && !\is_int($value)) || ($type === 'float' && !\is_float($value)) || ($type === 'bool' && !\is_bool($value))) {
throw new \BadMethodCallException('Container variable $' . $name . ' expected type ' . $type . ', but got ' . \gettype($value));
}

return $value;
Expand Down
Loading

0 comments on commit b63f5f7

Please sign in to comment.