diff --git a/src/Mapper/Object/CallbackObjectBuilder.php b/src/Mapper/Object/CallbackObjectBuilder.php index 813032a..c48033f 100644 --- a/src/Mapper/Object/CallbackObjectBuilder.php +++ b/src/Mapper/Object/CallbackObjectBuilder.php @@ -8,8 +8,6 @@ use CuyZ\Valinor\Definition\FunctionDefinition; use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage; use Exception; -use function array_values; - /** @internal */ final class CallbackObjectBuilder implements ObjectBuilder { @@ -40,9 +38,7 @@ final class CallbackObjectBuilder implements ObjectBuilder public function build(array $arguments): object { - // @PHP8.0 `array_values` can be removed - /** @infection-ignore-all */ - $arguments = array_values($arguments); + $arguments = new MethodArguments($this->function->parameters(), $arguments); try { return ($this->callback)(...$arguments); diff --git a/src/Mapper/Object/MethodArguments.php b/src/Mapper/Object/MethodArguments.php new file mode 100644 index 0000000..e534aae --- /dev/null +++ b/src/Mapper/Object/MethodArguments.php @@ -0,0 +1,50 @@ + + */ +final class MethodArguments implements IteratorAggregate +{ + /** @var list */ + private array $arguments = []; + + /** + * @param array $arguments + */ + public function __construct(Parameters $parameters, array $arguments) + { + foreach ($parameters as $parameter) { + $name = $parameter->name(); + + if (! array_key_exists($parameter->name(), $arguments) && ! $parameter->isOptional()) { + throw new MissingMethodArgument($parameter); + } + + if ($parameter->isVariadic()) { + // @PHP8.0 remove `array_values`? Behaviour might change, careful. + $this->arguments = [...$this->arguments, ...array_values($arguments[$name])]; // @phpstan-ignore-line we know that the argument is iterable + } else { + $this->arguments[] = $arguments[$name]; + } + } + } + + public function getIterator(): Traversable + { + // @PHP8.0 `array_values` can be removed + yield from array_values($this->arguments); + } +} diff --git a/src/Mapper/Object/MethodObjectBuilder.php b/src/Mapper/Object/MethodObjectBuilder.php index 70b3657..126cfc0 100644 --- a/src/Mapper/Object/MethodObjectBuilder.php +++ b/src/Mapper/Object/MethodObjectBuilder.php @@ -10,12 +10,9 @@ use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotPublic; use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotStatic; use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodClassReturnType; use CuyZ\Valinor\Mapper\Object\Exception\MethodNotFound; -use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument; use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage; use Exception; -use function array_values; - /** @api */ final class MethodObjectBuilder implements ObjectBuilder { @@ -64,36 +61,14 @@ final class MethodObjectBuilder implements ObjectBuilder public function build(array $arguments): object { - $values = []; - - foreach ($this->method->parameters() as $parameter) { - $name = $parameter->name(); - - if (! array_key_exists($parameter->name(), $arguments) && ! $parameter->isOptional()) { - throw new MissingMethodArgument($parameter); - } - - if ($parameter->isVariadic()) { - // @PHP8.0 remove `array_values`? Behaviour might change, careful. - $values = [...$values, ...array_values($arguments[$name])]; // @phpstan-ignore-line we know that the argument is iterable - } else { - $values[] = $arguments[$name]; - } - } - $className = $this->class->name(); $methodName = $this->method->name(); + $arguments = new MethodArguments($this->method->parameters(), $arguments); try { - // @PHP8.0 `array_values` can be removed - /** @infection-ignore-all */ - $values = array_values($values); - - if (! $this->method->isStatic()) { - return new $className(...$values); - } - - return $className::$methodName(...$values); // @phpstan-ignore-line + return $this->method->isStatic() + ? $className::$methodName(...$arguments) // @phpstan-ignore-line + : new $className(...$arguments); } catch (Exception $exception) { throw ThrowableMessage::from($exception); } diff --git a/tests/Unit/Mapper/Object/MethodArgumentsTest.php b/tests/Unit/Mapper/Object/MethodArgumentsTest.php new file mode 100644 index 0000000..418ba66 --- /dev/null +++ b/tests/Unit/Mapper/Object/MethodArgumentsTest.php @@ -0,0 +1,30 @@ +expectException(MissingMethodArgument::class); + $this->expectExceptionCode(1629468609); + $this->expectExceptionMessage("Missing argument `foo` of type `{$parameter->type()}`."); + + (new MethodArguments( + new Parameters($parameter), + [ + 'bar' => 'bar', + ] + )); + } +} diff --git a/tests/Unit/Mapper/Object/MethodObjectBuilderTest.php b/tests/Unit/Mapper/Object/MethodObjectBuilderTest.php index ce4b028..e523811 100644 --- a/tests/Unit/Mapper/Object/MethodObjectBuilderTest.php +++ b/tests/Unit/Mapper/Object/MethodObjectBuilderTest.php @@ -8,7 +8,6 @@ use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotPublic; use CuyZ\Valinor\Mapper\Object\Exception\ConstructorMethodIsNotStatic; use CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorMethodClassReturnType; use CuyZ\Valinor\Mapper\Object\Exception\MethodNotFound; -use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument; use CuyZ\Valinor\Mapper\Object\MethodObjectBuilder; use CuyZ\Valinor\Mapper\Tree\Message\ThrowableMessage; use CuyZ\Valinor\Tests\Fake\Definition\FakeClassDefinition; @@ -114,27 +113,6 @@ final class MethodObjectBuilderTest extends TestCase new MethodObjectBuilder($class, 'invalidConstructor'); } - public function test_missing_arguments_throws_exception(): void - { - $object = new class ('foo') { - public string $value; - - public function __construct(string $value) - { - $this->value = $value; - } - }; - - $class = FakeClassDefinition::fromReflection(new ReflectionClass($object)); - $objectBuilder = new MethodObjectBuilder($class, '__construct'); - - $this->expectException(MissingMethodArgument::class); - $this->expectExceptionCode(1629468609); - $this->expectExceptionMessage('Missing argument `Signature::value` of type `string`.'); - - $objectBuilder->build([]); - } - public function test_exception_thrown_by_constructor_is_caught_and_wrapped(): void { $class = FakeClassDefinition::fromReflection(new ReflectionClass(ObjectWithConstructorThatThrowsException::class));