Skip to content

Commit

Permalink
check readonly property via serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
garak committed Sep 30, 2024
1 parent 74ef282 commit ed20cf5
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 17 deletions.
3 changes: 2 additions & 1 deletion src/Mapping/ReflectionReadonlyProperty.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use function func_get_args;
use function func_num_args;
use function is_object;
use function serialize;
use function sprintf;

/** @internal */
Expand Down Expand Up @@ -42,7 +43,7 @@ public function setValue(mixed $objectOrValue, mixed $value = null): void

assert(is_object($objectOrValue));

if (parent::getValue($objectOrValue) !== $value) {
if (serialize(parent::getValue($objectOrValue)) !== serialize($value)) {
throw new LogicException(sprintf('Attempting to change readonly property %s::$%s.', $this->class, $this->name));
}
}
Expand Down
22 changes: 22 additions & 0 deletions tests/Tests/Models/ReadonlyProperties/Library.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\ReadonlyProperties;

use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Table;
use Doctrine\Tests\Models\ValueObjects\Uuid;

#[Entity]
#[Table(name: 'library')]
class Library
{
#[Column]
#[Id]
#[GeneratedValue(strategy: 'NONE')]
public readonly Uuid $uuid;
}
16 changes: 16 additions & 0 deletions tests/Tests/Models/ValueObjects/Uuid.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Doctrine\Tests\Models\ValueObjects;

class Uuid
{
/** @var string */
public $id;

public function __construct(string $id)
{
$this->id = $id;
}
}
82 changes: 66 additions & 16 deletions tests/Tests/ORM/Mapping/ReflectionReadonlyPropertyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
use Doctrine\ORM\Mapping\ReflectionReadonlyProperty;
use Doctrine\Tests\Models\CMS\CmsTag;
use Doctrine\Tests\Models\ReadonlyProperties\Author;
use Doctrine\Tests\Models\ReadonlyProperties\Library;
use Doctrine\Tests\Models\ValueObjects\Uuid;
use Generator;
use InvalidArgumentException;
use LogicException;
use PHPUnit\Framework\TestCase;
Expand All @@ -17,36 +20,83 @@
*/
class ReflectionReadonlyPropertyTest extends TestCase
{
public function testSecondWriteWithSameValue(): void
/**
* @dataProvider sameValueProvider
*/
public function testSecondWriteWithSameValue(object $entity, string $property, mixed $value, mixed $sameValue): void
{
$author = new Author();

$wrappedReflection = new ReflectionProperty($author, 'name');
$wrappedReflection = new ReflectionProperty($entity, $property);
$reflection = new ReflectionReadonlyProperty($wrappedReflection);

$reflection->setValue($author, 'John Doe');
$reflection->setValue($entity, $value);

self::assertSame('John Doe', $wrappedReflection->getValue($author));
self::assertSame('John Doe', $reflection->getValue($author));
self::assertSame($value, $wrappedReflection->getValue($entity));
self::assertSame($value, $reflection->getValue($entity));

$reflection->setValue($author, 'John Doe');
$reflection->setValue($entity, $sameValue);

self::assertSame('John Doe', $wrappedReflection->getValue($author));
self::assertSame('John Doe', $reflection->getValue($author));
/*
* Intentionally testing against the initial $value rather than the $sameValue that we just set above one in
* order to catch false positives when dealing with object types
*/
self::assertSame($value, $wrappedReflection->getValue($entity));
self::assertSame($value, $reflection->getValue($entity));
}

public function testSecondWriteWithDifferentValue(): void
public function sameValueProvider(): Generator
{
$author = new Author();
yield 'string' => [
'entity' => new Author(),
'property' => 'name',
'value' => 'John Doe',
'sameValue' => 'John Doe',
];

yield 'uuid' => [
'entity' => new Library(),
'property' => 'uuid',
'value' => new Uuid('438d5dc3-36c9-410a-88db-7a184856ebb8'),
'sameValue' => new Uuid('438d5dc3-36c9-410a-88db-7a184856ebb8'),
];
}

$wrappedReflection = new ReflectionProperty($author, 'name');
/**
* @dataProvider differentValueProvider
*/
public function testSecondWriteWithDifferentValue(
object $entity,
string $property,
mixed $value,
mixed $differentValue,
string $expectedExceptionMessage
): void {
$wrappedReflection = new ReflectionProperty($entity, $property);
$reflection = new ReflectionReadonlyProperty($wrappedReflection);

$reflection->setValue($author, 'John Doe');
$reflection->setValue($entity, $value);

$this->expectException(LogicException::class);
$this->expectExceptionMessage('Attempting to change readonly property Doctrine\Tests\Models\ReadonlyProperties\Author::$name.');
$reflection->setValue($author, 'Jane Doe');
$this->expectExceptionMessage($expectedExceptionMessage);
$reflection->setValue($entity, $differentValue);
}

public function differentValueProvider(): Generator
{
yield 'string' => [
'entity' => new Author(),
'property' => 'name',
'value' => 'John Doe',
'differentValue' => 'Jane Doe',
'expectedExceptionMessage' => 'Attempting to change readonly property Doctrine\Tests\Models\ReadonlyProperties\Author::$name.',
];

yield 'uuid' => [
'entity' => new Library(),
'property' => 'uuid',
'value' => new Uuid('438d5dc3-36c9-410a-88db-7a184856ebb8'),
'differentValue' => new Uuid('5d5049ee-01fd-4b66-9f82-9f637fff6a7d'),
'expectedExceptionMessage' => 'Attempting to change readonly property Doctrine\Tests\Models\ReadonlyProperties\Library::$uuid.',
];
}

public function testNonReadonlyPropertiesAreForbidden(): void
Expand Down

0 comments on commit ed20cf5

Please sign in to comment.