mirror of
https://github.com/danog/Valinor.git
synced 2024-11-30 04:39:05 +01:00
fix: handle variadic arguments in callable constructors
This commit is contained in:
parent
e2451df2c1
commit
b646ccecf2
@ -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);
|
||||
|
50
src/Mapper/Object/MethodArguments.php
Normal file
50
src/Mapper/Object/MethodArguments.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Mapper\Object;
|
||||
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
use function array_values;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @implements IteratorAggregate<mixed>
|
||||
*/
|
||||
final class MethodArguments implements IteratorAggregate
|
||||
{
|
||||
/** @var list<mixed> */
|
||||
private array $arguments = [];
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
30
tests/Unit/Mapper/Object/MethodArgumentsTest.php
Normal file
30
tests/Unit/Mapper/Object/MethodArgumentsTest.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CuyZ\Valinor\Tests\Unit\Mapper\Object;
|
||||
|
||||
use CuyZ\Valinor\Definition\Parameters;
|
||||
use CuyZ\Valinor\Mapper\Object\Exception\MissingMethodArgument;
|
||||
use CuyZ\Valinor\Mapper\Object\MethodArguments;
|
||||
use CuyZ\Valinor\Tests\Fake\Definition\FakeParameterDefinition;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class MethodArgumentsTest extends TestCase
|
||||
{
|
||||
public function test_missing_arguments_throws_exception(): void
|
||||
{
|
||||
$parameter = FakeParameterDefinition::new('foo');
|
||||
|
||||
$this->expectException(MissingMethodArgument::class);
|
||||
$this->expectExceptionCode(1629468609);
|
||||
$this->expectExceptionMessage("Missing argument `foo` of type `{$parameter->type()}`.");
|
||||
|
||||
(new MethodArguments(
|
||||
new Parameters($parameter),
|
||||
[
|
||||
'bar' => 'bar',
|
||||
]
|
||||
));
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
Loading…
Reference in New Issue
Block a user