diff --git a/src/Library/Container.php b/src/Library/Container.php index c87823e0..8ba56010 100644 --- a/src/Library/Container.php +++ b/src/Library/Container.php @@ -130,7 +130,8 @@ public function __construct(Settings $settings) $this->get(ClassDefinitionRepository::class), $this->get(ObjectBuilderFactory::class), $this->get(ObjectNodeBuilder::class), - $settings->enableFlexibleCasting + $settings->enableFlexibleCasting, + $settings->allowSuperfluousKeys, ); $builder = new CasterProxyNodeBuilder($builder); diff --git a/src/Mapper/Object/ArgumentsValues.php b/src/Mapper/Object/ArgumentsValues.php index 6a47e0ec..f2d7aee4 100644 --- a/src/Mapper/Object/ArgumentsValues.php +++ b/src/Mapper/Object/ArgumentsValues.php @@ -36,22 +36,22 @@ private function __construct(Arguments $arguments) $this->arguments = $arguments; } - public static function forInterface(Arguments $arguments, mixed $value): self + public static function forInterface(Arguments $arguments, mixed $value, bool $allowSuperfluousKeys): self { $self = new self($arguments); $self->forInterface = true; if (count($arguments) > 0) { - $self = $self->transform($value); + $self = $self->transform($value, $allowSuperfluousKeys); } return $self; } - public static function forClass(Arguments $arguments, mixed $value): self + public static function forClass(Arguments $arguments, mixed $value, bool $allowSuperfluousKeys): self { $self = new self($arguments); - $self = $self->transform($value); + $self = $self->transform($value, $allowSuperfluousKeys); return $self; } @@ -82,11 +82,11 @@ public function hadSingleArgument(): bool return $this->hadSingleArgument; } - private function transform(mixed $value): self + private function transform(mixed $value, bool $allowSuperfluousKeys): self { $clone = clone $this; - $transformedValue = $this->transformValueForSingleArgument($value); + $transformedValue = $this->transformValueForSingleArgument($value, $allowSuperfluousKeys); if (! is_array($transformedValue)) { throw new InvalidSource($transformedValue, $this->arguments); @@ -109,7 +109,7 @@ private function transform(mixed $value): self return $clone; } - private function transformValueForSingleArgument(mixed $value): mixed + private function transformValueForSingleArgument(mixed $value, bool $allowSuperfluousKeys): mixed { if (count($this->arguments) !== 1) { return $value; @@ -122,7 +122,7 @@ private function transformValueForSingleArgument(mixed $value): mixed && $type->keyType() !== ArrayKeyType::integer(); if (is_array($value) && array_key_exists($name, $value)) { - if ($this->forInterface || ! $isTraversableAndAllowsStringKeys || count($value) === 1) { + if ($this->forInterface || ! $isTraversableAndAllowsStringKeys || $allowSuperfluousKeys || count($value) === 1) { return $value; } } diff --git a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php index 93f3e893..c513883a 100644 --- a/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php +++ b/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php @@ -26,7 +26,8 @@ public function __construct( private ClassDefinitionRepository $classDefinitionRepository, private ObjectBuilderFactory $objectBuilderFactory, private ObjectNodeBuilder $objectNodeBuilder, - private bool $enableFlexibleCasting + private bool $enableFlexibleCasting, + private bool $allowSuperfluousKeys, ) {} public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode @@ -116,7 +117,7 @@ private function transformSourceForClass(Shell $shell, Arguments $interfaceArgum */ private function children(Shell $shell, Arguments $arguments, RootNodeBuilder $rootBuilder): array { - $arguments = ArgumentsValues::forInterface($arguments, $shell->value()); + $arguments = ArgumentsValues::forInterface($arguments, $shell->value(), $this->allowSuperfluousKeys); $children = []; diff --git a/src/Mapper/Tree/Builder/ObjectNodeBuilder.php b/src/Mapper/Tree/Builder/ObjectNodeBuilder.php index e0965e5b..d060de97 100644 --- a/src/Mapper/Tree/Builder/ObjectNodeBuilder.php +++ b/src/Mapper/Tree/Builder/ObjectNodeBuilder.php @@ -18,7 +18,7 @@ public function __construct(private bool $allowSuperfluousKeys) {} public function build(ObjectBuilder $builder, Shell $shell, RootNodeBuilder $rootBuilder): TreeNode { - $arguments = ArgumentsValues::forClass($builder->describeArguments(), $shell->value()); + $arguments = ArgumentsValues::forClass($builder->describeArguments(), $shell->value(), $this->allowSuperfluousKeys); $children = $this->children($shell, $arguments, $rootBuilder); diff --git a/tests/Integration/Mapping/Other/SuperfluousKeysMappingTest.php b/tests/Integration/Mapping/Other/SuperfluousKeysMappingTest.php index 489360b1..e02816f5 100644 --- a/tests/Integration/Mapping/Other/SuperfluousKeysMappingTest.php +++ b/tests/Integration/Mapping/Other/SuperfluousKeysMappingTest.php @@ -105,6 +105,26 @@ public function test_single_list_property_node_can_be_mapped_(): void self::assertSame([7, 8, 9], $result->values); } + + public function test_object_with_one_array_property_can_be_mapped_when_superfluous_key_is_present(): void + { + $class = new class () { + /** @var array */ + public array $values; + }; + + try { + $result = $this->mapper->map($class::class, [ + 'values' => ['foo', 'bar'], + 'superfluous_key' => 'useless value', + ]); + } catch (MappingError $error) { + $this->mappingFail($error); + } + + self::assertSame('foo', $result->values[0]); + self::assertSame('bar', $result->values[1]); + } } final class UnionOfBarAndFizAndFoo