diff --git a/CHANGELOG.md b/CHANGELOG.md index f692d2541..df1b51518 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The major version bump is due to upping the required PHP version from `8.1` to ` * It is now possible to customize how JSON request bodies are decoded using the following methods: - `Body::setJsonMaxDepth()` to set the maximum JSON nesting level. - `Body::setJsonFlags()` to set the JSON decoding options. +* Added support for resolving intersection types to the dependency injection container. * Added `Deprecated` middleware that allows you to easily set the `Deprecation` and `Sunset` HTTP headers. * Global constraints and middleware will now be listed and sorted by priority when using the `app:routes` command. diff --git a/src/mako/functions.php b/src/mako/functions.php index 9317f4bd0..4f5d66705 100644 --- a/src/mako/functions.php +++ b/src/mako/functions.php @@ -5,35 +5,47 @@ * @license http://www.makoframework.com/license */ -namespace mako; +namespace mako { + use function filter_var; + use function getenv; + use function json_encode; + use function substr; + + /** + * Builds and returns a "function" used for middleware, route constraints and validation rules. + */ + function f(string $_name, mixed ...$_arguments): string + { + if (empty($_arguments)) { + return $_name; + } + + return "{$_name}(" . substr(json_encode($_arguments), 1, -1) . ')'; + } -use function filter_var; -use function getenv; -use function json_encode; -use function substr; + /** + * Returns the value of the chosen environment variable or NULL if it does not exist. + */ + function env(string $variableName, mixed $default = null, bool $isBool = false, bool $localOnly = false): mixed + { + $value = $_ENV[$variableName] ?? (getenv($variableName, $localOnly) ?: null); -/** - * Builds and returns a "function" used for middleware, route constraints and validation rules. - */ -function f(string $_name, mixed ...$_arguments): string -{ - if (empty($_arguments)) { - return $_name; - } + if ($isBool && $value !== true && $value !== false && $value !== null) { + $value = filter_var($value, FILTER_VALIDATE_BOOL); + } - return "{$_name}(" . substr(json_encode($_arguments), 1, -1) . ')'; + return $value ?? $default; + } } -/** - * Returns the value of the chosen environment variable or NULL if it does not exist. - */ -function env(string $variableName, mixed $default = null, bool $isBool = false, bool $localOnly = false): mixed -{ - $value = $_ENV[$variableName] ?? (getenv($variableName, $localOnly) ?: null); +namespace mako\syringe { + use function implode; - if ($isBool && $value !== true && $value !== false && $value !== null) { - $value = filter_var($value, FILTER_VALIDATE_BOOL); + /** + * Returns the string representation of the intersection of the provided types. + */ + function intersection(string ...$types): string + { + return implode('&', $types); } - - return $value ?? $default; } diff --git a/src/mako/syringe/Container.php b/src/mako/syringe/Container.php index dc1ea0b8d..135f24a8a 100755 --- a/src/mako/syringe/Container.php +++ b/src/mako/syringe/Container.php @@ -15,6 +15,7 @@ use mako\syringe\traits\ContainerAwareTrait; use ReflectionClass; use ReflectionFunction; +use ReflectionIntersectionType; use ReflectionMethod; use ReflectionNamedType; use ReflectionParameter; @@ -251,7 +252,18 @@ protected function resolveParameter(ReflectionParameter $parameter, ?ReflectionC // If the parameter should be a class instance then we'll try to resolve it using the container - $parameterClassName = ($parameterType instanceof ReflectionNamedType && !$parameterType->isBuiltin()) ? $parameterType->getName() : null; + $parameterClassName = null; + + if ($parameterType instanceof ReflectionNamedType && !$parameterType->isBuiltin()) { + $parameterClassName = $parameterType->getName(); + } + elseif ($parameterType instanceof ReflectionIntersectionType) { + $parameterClassName = (string) $parameterType; + + if (!$this->has($parameterClassName)) { + $parameterClassName = null; + } + } if ($parameterClassName !== null) { if ($class !== null) { diff --git a/tests/unit/FunctionsTest.php b/tests/unit/FunctionsTest.php index aa1f5df15..142912b29 100644 --- a/tests/unit/FunctionsTest.php +++ b/tests/unit/FunctionsTest.php @@ -11,6 +11,7 @@ use function mako\env; use function mako\f; +use function mako\syringe\intersection; /** * @group unit @@ -82,4 +83,12 @@ public function testEnvWithBooleanValues(): void $this->assertTrue(env('MAKO_TRUE', isBool: true)); $this->assertFalse(env('MAKO_FALSE', isBool: true)); } + + /** + * + */ + public function testIntersection(): void + { + $this->assertSame('foo&bar&baz', intersection('foo', 'bar', 'baz')); + } } diff --git a/tests/unit/syringe/ContainerTest.php b/tests/unit/syringe/ContainerTest.php index 304830867..057d76a2c 100755 --- a/tests/unit/syringe/ContainerTest.php +++ b/tests/unit/syringe/ContainerTest.php @@ -14,6 +14,8 @@ use mako\tests\TestCase; use stdClass; +use function mako\syringe\intersection; + // -------------------------------------------------------------------------- // START CLASSES // -------------------------------------------------------------------------- @@ -175,6 +177,29 @@ public function __construct( } } +interface IA +{ + +} + +interface IB +{ + +} + +class AB implements IA, IB +{ + +} + +class Intersection +{ + public function __construct( + public IA&IB $ab + ) { + } +} + // -------------------------------------------------------------------------- // END CLASSES // -------------------------------------------------------------------------- @@ -856,4 +881,32 @@ public function testRemoveInstance(): void $this->assertEmpty($container->getInstanceClassNames()); } + + /** + * + */ + public function testResolveIntersectionType(): void + { + $container = new Container; + + $container->register(intersection(IA::class, IB::class), AB::class); + + $object = $container->get(Intersection::class); + + $this->assertInstanceOf(AB::class, $object->ab); + } + + /** + * + */ + public function testResolveIntersectionTypeWithoutRegisteredHint(): void + { + $this->expectException(UnableToResolveParameterException::class); + + $container = new Container; + + $object = $container->get(Intersection::class); + + $this->assertInstanceOf(AB::class, $object->ab); + } }