Skip to content

Commit 36d4512

Browse files
authored
Merge pull request #204 from patchlevel/2.0.x-merge-up-into-2.1.x_AZZUHwqF
Merge release 2.0.1 into 2.1.x
2 parents a31791f + dc71f77 commit 36d4512

3 files changed

Lines changed: 102 additions & 6 deletions

File tree

‎phpstan-baseline.neon‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,18 @@ parameters:
192192
count: 2
193193
path: tests/Unit/Normalizer/ArrayNormalizerTest.php
194194

195+
-
196+
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with array\{1, 2, 3\} and array\{1, 2, 3\} will always evaluate to true\.$#'
197+
identifier: staticMethod.alreadyNarrowedType
198+
count: 1
199+
path: tests/Unit/Normalizer/ArrayNormalizerTest.php
200+
201+
-
202+
message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertSame\(\) with array\{101, 102, 103\} and array\{101, 102, 103\} will always evaluate to true\.$#'
203+
identifier: staticMethod.alreadyNarrowedType
204+
count: 1
205+
path: tests/Unit/Normalizer/ArrayNormalizerTest.php
206+
195207
-
196208
message: '#^Cannot cast mixed to int\.$#'
197209
identifier: cast.int

‎src/Normalizer/ArrayNormalizer.php‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ public function normalize(mixed $value, array $context): array|null
3535
throw InvalidArgument::withWrongType('array|null', $value);
3636
}
3737

38-
foreach ($value as &$item) {
39-
$item = $this->normalizer->normalize($item, $context);
38+
$result = [];
39+
40+
foreach ($value as $key => $item) {
41+
$result[$key] = $this->normalizer->normalize($item, $context);
4042
}
4143

42-
return $value;
44+
return $result;
4345
}
4446

4547
/**
@@ -57,11 +59,13 @@ public function denormalize(mixed $value, array $context): array|null
5759
throw InvalidArgument::withWrongType('array|null', $value);
5860
}
5961

60-
foreach ($value as &$item) {
61-
$item = $this->normalizer->denormalize($item, $context);
62+
$result = [];
63+
64+
foreach ($value as $key => $item) {
65+
$result[$key] = $this->normalizer->denormalize($item, $context);
6266
}
6367

64-
return $value;
68+
return $result;
6569
}
6670

6771
public function setHydrator(Hydrator $hydrator): void

‎tests/Unit/Normalizer/ArrayNormalizerTest.php‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Patchlevel\Hydrator\Tests\Unit\Normalizer;
66

7+
use InvalidArgumentException;
78
use Patchlevel\Hydrator\Hydrator;
89
use Patchlevel\Hydrator\Normalizer\ArrayNormalizer;
910
use Patchlevel\Hydrator\Normalizer\HydratorAwareNormalizer;
@@ -12,6 +13,8 @@
1213
use PHPUnit\Framework\Attributes\CoversClass;
1314
use PHPUnit\Framework\TestCase;
1415

16+
use function is_int;
17+
1518
#[CoversClass(ArrayNormalizer::class)]
1619
final class ArrayNormalizerTest extends TestCase
1720
{
@@ -155,6 +158,83 @@ public function denormalize(mixed $value, array $context = []): mixed
155158
$this->assertSame([$context, $context], $innerNormalizer->contexts);
156159
}
157160

161+
public function testNormalizeDoesNotMutateSourceArrayWithReferencedElement(): void
162+
{
163+
$innerNormalizer = new class implements Normalizer {
164+
/** @param array<string, mixed> $context */
165+
public function normalize(mixed $value, array $context): int
166+
{
167+
if (!is_int($value)) {
168+
throw new InvalidArgumentException();
169+
}
170+
171+
return $value + 100;
172+
}
173+
174+
/** @param array<string, mixed> $context */
175+
public function denormalize(mixed $value, array $context): int
176+
{
177+
if (!is_int($value)) {
178+
throw new InvalidArgumentException();
179+
}
180+
181+
return $value - 100;
182+
}
183+
};
184+
185+
$source = [1, 2, 3];
186+
187+
// A leftover reference from a previous `foreach ($source as &$row)` without
188+
// unset() turns the last element into a PHP reference. Iterating the source
189+
// by reference inside the normalizer must not write back into it.
190+
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
191+
foreach ($source as &$row) {
192+
}
193+
194+
$normalizer = new ArrayNormalizer($innerNormalizer);
195+
$result = $normalizer->normalize($source, []);
196+
197+
self::assertSame([101, 102, 103], $result);
198+
self::assertSame([1, 2, 3], $source);
199+
}
200+
201+
public function testDenormalizeDoesNotMutateSourceArrayWithReferencedElement(): void
202+
{
203+
$innerNormalizer = new class implements Normalizer {
204+
/** @param array<string, mixed> $context */
205+
public function normalize(mixed $value, array $context): int
206+
{
207+
if (!is_int($value)) {
208+
throw new InvalidArgumentException();
209+
}
210+
211+
return $value + 100;
212+
}
213+
214+
/** @param array<string, mixed> $context */
215+
public function denormalize(mixed $value, array $context): int
216+
{
217+
if (!is_int($value)) {
218+
throw new InvalidArgumentException();
219+
}
220+
221+
return $value - 100;
222+
}
223+
};
224+
225+
$source = [101, 102, 103];
226+
227+
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedForeach
228+
foreach ($source as &$row) {
229+
}
230+
231+
$normalizer = new ArrayNormalizer($innerNormalizer);
232+
$result = $normalizer->denormalize($source, []);
233+
234+
self::assertSame([1, 2, 3], $result);
235+
self::assertSame([101, 102, 103], $source);
236+
}
237+
158238
public function testPassHydrator(): void
159239
{
160240
$hydrator = $this->createMock(Hydrator::class);

0 commit comments

Comments
 (0)