Skip to content

Commit

Permalink
Failing test case: Valinor behaves differently on ignored additional …
Browse files Browse the repository at this point in the history
…keys

In this test, Valinor takes a `__construct(list<int> $values)` and behaves differently:

 * OK with `array{values: list<int>}`
 * OK with `list<int>`
 * BROKEN with `array{values: list<int>, ...}` (additional keys)

This behavior was initially found by @Tigerman55, and affects de-serialization in a complex
nested structure.

A workaround that was found to make Valinor consistent is to add an unused argument to the
constructor, which seems to make Valinor's behavior more consistent:

```diff
    /** @param list<int> $values */
-    public function __construct(public readonly array $values)
+    public function __construct(public readonly array $values, bool $_ignored = false)
```
  • Loading branch information
Ocramius committed Mar 1, 2024
1 parent 4b68835 commit c1197ed
Showing 1 changed file with 78 additions and 0 deletions.
78 changes: 78 additions & 0 deletions tests/Integration/Mapping/Object/ListConstructorMapping.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Tests\Integration\Mapping\Object;

use CuyZ\Valinor\Mapper\MappingError;
use CuyZ\Valinor\Tests\Integration\IntegrationTestCase;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject;
use CuyZ\Valinor\Tests\Integration\Mapping\Fixture\SimpleObject as SimpleObjectAlias;

final class ListConstructorMapping extends IntegrationTestCase
{
public function test_it_will_deserialize_a_hashmap_into_an_object_with_a_list_as_constructor_argument(): void
{
$mapper = $this
->mapperBuilder()
->allowPermissiveTypes()
->allowSuperfluousKeys()
->mapper();

self::assertEquals(
[7, 8, 9],
$mapper->map(
ClassWithSingleListConstructorArgument::class,
[
'values' => [7, 8, 9],
]
)->values
);
}

public function test_it_will_deserialize_a_hashmap_with_additional_keys_into_an_object_with_a_list_as_constructor_argument(): void
{
$mapper = $this
->mapperBuilder()
->allowPermissiveTypes()
->allowSuperfluousKeys()
->mapper();

self::assertSame(
[7, 8, 9],
$mapper->map(
ClassWithSingleListConstructorArgument::class,
[
'unrelated_key' => 'this-should-be-ignored-and-have-no-effect',
'values' => [7, 8, 9],
]
)->values
);
}

public function test_it_will_deserialize_a_list_into_an_object_with_a_list_as_constructor_argument(): void
{
$mapper = $this
->mapperBuilder()
->allowPermissiveTypes()
->allowSuperfluousKeys()
->mapper();

self::assertEquals(
[7, 8, 9],
$mapper->map(
ClassWithSingleListConstructorArgument::class,
[7, 8, 9]
)->values
);
}
}

final class ClassWithSingleListConstructorArgument
{
/** @param list<int> $values */
public function __construct(public readonly array $values)
{
}
}

0 comments on commit c1197ed

Please sign in to comment.