From 5923e574192fcd8a2f05dae3f6d65d8305807291 Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 2 Jan 2022 13:56:39 +0100 Subject: [PATCH] Skip writing readonly properties if the value did not change --- .../ORM/Mapping/ClassMetadataInfo.php | 31 ++++++++--- .../Mapping/ReflectionReadonlyProperty.php | 51 +++++++++++++++++++ psalm-baseline.xml | 2 +- psalm.xml | 6 +++ 4 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php diff --git a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php index 0359c2731da..c95461030bd 100644 --- a/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php +++ b/lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php @@ -705,7 +705,7 @@ class ClassMetadataInfo implements ClassMetadata /** * The ReflectionProperty instances of the mapped class. * - * @var ReflectionProperty[]|null[] + * @var array */ public $reflFields = []; @@ -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'] ); @@ -1007,7 +1008,8 @@ public function wakeupReflection($reflService) continue; } - $fieldRefl = $reflService->getAccessibleProperty( + $fieldRefl = $this->getAccessibleProperty( + $reflService, $embeddedClass['declared'] ?? $this->name, $property ); @@ -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( @@ -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); } } @@ -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; + } } diff --git a/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php b/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php new file mode 100644 index 00000000000..87cf4245fb6 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/ReflectionReadonlyProperty.php @@ -0,0 +1,51 @@ +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)); + } + } +} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index d5aacf2cf8d..2951933684e 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -820,7 +820,7 @@ $parentReflFields[$embeddedClass['declaredField']] $parentReflFields[$mapping['declaredField']] $queryMapping['resultClass'] - $reflService->getAccessibleProperty($mapping['originalClass'], $mapping['originalField']) + $this->getAccessibleProperty($reflService, $mapping['originalClass'], $mapping['originalField']) $embeddable->reflClass->name diff --git a/psalm.xml b/psalm.xml index 57dabceddeb..5332ca03b6a 100644 --- a/psalm.xml +++ b/psalm.xml @@ -75,6 +75,12 @@ + + + + + +