Skip to content

Commit

Permalink
Skip writing readonly properties if the value did not change
Browse files Browse the repository at this point in the history
  • Loading branch information
derrabus committed Jan 9, 2022
1 parent 20d0298 commit 5923e57
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 9 deletions.
31 changes: 23 additions & 8 deletions lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,7 @@ class ClassMetadataInfo implements ClassMetadata
/**
* The ReflectionProperty instances of the mapped class.
*
* @var ReflectionProperty[]|null[]
* @var array<string, ReflectionProperty|null>
*/
public $reflFields = [];

Expand Down Expand Up @@ -993,7 +993,8 @@ public function wakeupReflection($reflService)

foreach ($this->embeddedClasses as $property => $embeddedClass) {
if (isset($embeddedClass['declaredField'])) {
$childProperty = $reflService->getAccessibleProperty(
$childProperty = $this->getAccessibleProperty(
$reflService,
$this->embeddedClasses[$embeddedClass['declaredField']]['class'],
$embeddedClass['originalField']
);
Expand All @@ -1007,7 +1008,8 @@ public function wakeupReflection($reflService)
continue;
}

$fieldRefl = $reflService->getAccessibleProperty(
$fieldRefl = $this->getAccessibleProperty(
$reflService,
$embeddedClass['declared'] ?? $this->name,
$property
);
Expand All @@ -1020,15 +1022,15 @@ public function wakeupReflection($reflService)
if (isset($mapping['declaredField']) && isset($parentReflFields[$mapping['declaredField']])) {
$this->reflFields[$field] = new ReflectionEmbeddedProperty(
$parentReflFields[$mapping['declaredField']],
$reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']),
$this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']),
$mapping['originalClass']
);
continue;
}

$this->reflFields[$field] = isset($mapping['declared'])
? $reflService->getAccessibleProperty($mapping['declared'], $field)
: $reflService->getAccessibleProperty($this->name, $field);
? $this->getAccessibleProperty($reflService, $mapping['declared'], $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);

if (isset($mapping['enumType']) && $this->reflFields[$field] !== null) {
$this->reflFields[$field] = new ReflectionEnumProperty(
Expand All @@ -1040,8 +1042,8 @@ public function wakeupReflection($reflService)

foreach ($this->associationMappings as $field => $mapping) {
$this->reflFields[$field] = isset($mapping['declared'])
? $reflService->getAccessibleProperty($mapping['declared'], $field)
: $reflService->getAccessibleProperty($this->name, $field);
? $this->getAccessibleProperty($reflService, $mapping['declared'], $field)
: $this->getAccessibleProperty($reflService, $this->name, $field);
}
}

Expand Down Expand Up @@ -3779,4 +3781,17 @@ private function assertMappingOrderBy(array $mapping): void
throw new InvalidArgumentException("'orderBy' is expected to be an array, not " . gettype($mapping['orderBy']));
}
}

/**
* @psalm-param class-string $class
*/
private function getAccessibleProperty(ReflectionService $reflService, string $class, string $field): ?ReflectionProperty
{
$reflectionProperty = $reflService->getAccessibleProperty($class, $field);
if ($reflectionProperty !== null && PHP_VERSION_ID >= 80100 && $reflectionProperty->isReadOnly()) {
$reflectionProperty = new ReflectionReadonlyProperty($reflectionProperty);
}

return $reflectionProperty;
}
}
51 changes: 51 additions & 0 deletions lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Doctrine\ORM\Mapping;

use InvalidArgumentException;
use LogicException;
use ReflectionProperty;

use function assert;
use function func_get_args;
use function func_num_args;
use function is_object;
use function sprintf;

/**
* @internal
*/
final class ReflectionReadonlyProperty extends ReflectionProperty
{
public function __construct(
private ReflectionProperty $wrappedProperty
) {
if (! $wrappedProperty->isReadOnly()) {
throw new InvalidArgumentException('Given property is not readonly.');
}

parent::__construct($wrappedProperty->class, $wrappedProperty->name);
}

public function getValue(?object $object = null): mixed
{
return $this->wrappedProperty->getValue(...func_get_args());
}

public function setValue(mixed $objectOrValue, mixed $value = null): void
{
if (func_num_args() < 2 || $objectOrValue === null || ! $this->isInitialized($objectOrValue)) {
$this->wrappedProperty->setValue(...func_get_args());

return;
}

assert(is_object($objectOrValue));

if (parent::getValue($objectOrValue) !== $value) {
throw new LogicException(sprintf('Attempting to change readonly property %s::%s.', $this->class, $this->name));
}
}
}
2 changes: 1 addition & 1 deletion psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@
<code>$parentReflFields[$embeddedClass['declaredField']]</code>
<code>$parentReflFields[$mapping['declaredField']]</code>
<code>$queryMapping['resultClass']</code>
<code>$reflService-&gt;getAccessibleProperty($mapping['originalClass'], $mapping['originalField'])</code>
<code>$this-&gt;getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField'])</code>
</PossiblyNullArgument>
<PossiblyNullPropertyFetch occurrences="2">
<code>$embeddable-&gt;reflClass-&gt;name</code>
Expand Down
6 changes: 6 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@
<referencedClass name="Doctrine\DBAL\Platforms\PostgreSQLPlatform" />
</errorLevel>
</InvalidClass>
<MethodSignatureMismatch>
<errorLevel type="suppress">
<!-- Psalm is wrong about ReflectionProperty signatures. -->
<file name="lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php"/>
</errorLevel>
</MethodSignatureMismatch>
<MissingDependency>
<errorLevel type="suppress">
<!-- DBAL 3.2 forward compatibility -->
Expand Down

0 comments on commit 5923e57

Please sign in to comment.