Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d20a7c2

Browse files
[DeepClone] Idempotent readonly hydrate
1 parent 149599d commit d20a7c2

2 files changed

Lines changed: 40 additions & 1 deletion

File tree

src/DeepClone/DeepClone.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1371,7 +1371,17 @@ private static function getSimpleHydrator(string $class, int $flags = 0): \Closu
13711371
: $propertyReflector->setRawValue(...);
13721372
}
13731373
} elseif ($propertyReflector->isReadOnly()) {
1374-
$notByRef->{$propertyReflector->name} = $propertyReflector->setValue(...);
1374+
$notByRef->{$propertyReflector->name} = static function ($object, $value) use ($propertyReflector) {
1375+
/* Idempotent hydrate: if the readonly slot already holds
1376+
* the same value (===), silently skip. Avoids "Cannot
1377+
* modify readonly property" on no-op rehydration. */
1378+
if ($propertyReflector->isInitialized($object)
1379+
&& $propertyReflector->getValue($object) === $value)
1380+
{
1381+
return;
1382+
}
1383+
$propertyReflector->setValue($object, $value);
1384+
};
13751385
}
13761386
}
13771387

tests/DeepClone/DeepCloneTest.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,4 +1599,33 @@ public function testHydrateFlagsUnknownBitThrows()
15991599
deepclone_hydrate(\stdClass::class, [], [], 1 << 30);
16001600
}
16011601

1602+
public function testHydrateReadonlyIdempotentSkipsSameValue()
1603+
{
1604+
$obj = new HydrateReadonly(123);
1605+
$obj = deepclone_hydrate($obj, [HydrateReadonly::class => ['value' => 123]]);
1606+
$this->assertSame(123, $obj->getValue());
1607+
}
1608+
1609+
public function testHydrateReadonlyDifferentValueThrows()
1610+
{
1611+
$obj = new HydrateReadonly(123);
1612+
try {
1613+
deepclone_hydrate($obj, [HydrateReadonly::class => ['value' => 456]]);
1614+
$this->fail('Expected Error on readonly overwrite');
1615+
} catch (\Error $e) {
1616+
$this->assertStringContainsString('readonly', $e->getMessage());
1617+
}
1618+
$this->assertSame(123, $obj->getValue());
1619+
}
1620+
1621+
public function testHydrateReadonlyObjectIdentityIsSkipped()
1622+
{
1623+
$inner = new \stdClass();
1624+
$host = new class ($inner) {
1625+
public function __construct(public readonly \stdClass $o) {}
1626+
};
1627+
$class = $host::class;
1628+
$host = deepclone_hydrate($host, [$class => ['o' => $inner]]);
1629+
$this->assertSame($inner, $host->o);
1630+
}
16021631
}

0 commit comments

Comments
 (0)