From 238fb740284c22142019c59703717efff00778ef Mon Sep 17 00:00:00 2001
From: Benjamin Eberlei <kontakt@beberlei.de>
Date: Sat, 7 Dec 2024 00:37:59 +0100
Subject: [PATCH] Add RawValuePropertyAccessor to see how it will look in 8.4,
 pre support for lazy objects.

---
 src/Mapping/ClassMetadata.php                 |  8 ++-
 .../RawValuePropertyAccessor.php              | 52 +++++++++++++++++++
 2 files changed, 59 insertions(+), 1 deletion(-)
 create mode 100644 src/Mapping/PropertyAccessors/RawValuePropertyAccessor.php

diff --git a/src/Mapping/ClassMetadata.php b/src/Mapping/ClassMetadata.php
index e9f01e62b69..aff76fe8c05 100644
--- a/src/Mapping/ClassMetadata.php
+++ b/src/Mapping/ClassMetadata.php
@@ -18,6 +18,7 @@
 use Doctrine\ORM\Mapping\PropertyAccessors\EnumPropertyAccessor;
 use Doctrine\ORM\Mapping\PropertyAccessors\ObjectCastPropertyAccessor;
 use Doctrine\ORM\Mapping\PropertyAccessors\PropertyAccessor;
+use Doctrine\ORM\Mapping\PropertyAccessors\RawValuePropertyAccessor;
 use Doctrine\ORM\Mapping\PropertyAccessors\ReadonlyAccessor;
 use Doctrine\ORM\Mapping\PropertyAccessors\TypedNoDefaultPropertyAccessor;
 use Doctrine\Persistence\Mapping\ClassMetadata as PersistenceClassMetadata;
@@ -62,6 +63,8 @@
 use function trait_exists;
 use function trim;
 
+use const PHP_VERSION_ID;
+
 /**
  * A <tt>ClassMetadata</tt> instance holds all the object-relational mapping metadata
  * of an entity and its associations.
@@ -2689,7 +2692,10 @@ public function getSequencePrefix(AbstractPlatform $platform): string
     private function createPropertyAccessor(string $className, string $propertyName): PropertyAccessor
     {
         $reflectionProperty = new ReflectionProperty($className, $propertyName);
-        $accessor           = ObjectCastPropertyAccessor::fromReflectionProperty($reflectionProperty);
+
+        $accessor = PHP_VERSION_ID >= 80400
+            ? RawValuePropertyAccessor::fromReflectionProperty($reflectionProperty)
+            : ObjectCastPropertyAccessor::fromReflectionProperty($reflectionProperty);
 
         if ($reflectionProperty->hasType() && ! $reflectionProperty->getType()->allowsNull()) {
             $accessor = new TypedNoDefaultPropertyAccessor($accessor, $reflectionProperty);
diff --git a/src/Mapping/PropertyAccessors/RawValuePropertyAccessor.php b/src/Mapping/PropertyAccessors/RawValuePropertyAccessor.php
new file mode 100644
index 00000000000..27d3cf6ab87
--- /dev/null
+++ b/src/Mapping/PropertyAccessors/RawValuePropertyAccessor.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Doctrine\ORM\Mapping\PropertyAccessors;
+
+use Doctrine\ORM\Proxy\InternalProxy;
+use ReflectionProperty;
+
+use function ltrim;
+
+/**
+ * This is a PHP 8.4 and up only class and replaces ObjectCastPropertyAccessor.
+ *
+ * It works based on the raw values of a property, which for a case of property hooks
+ * is the backed value. If we kept using setValue/getValue, this would go through the hooks,
+ * which potentially change the data.
+ */
+class RawValuePropertyAccessor implements PropertyAccessor
+{
+    public static function fromReflectionProperty(ReflectionProperty $reflectionProperty): self
+    {
+        $name = $reflectionProperty->getName();
+        $key  = $reflectionProperty->isPrivate() ? "\0" . ltrim($reflectionProperty->getDeclaringClass()->getName(), '\\') . "\0" . $name : ($reflectionProperty->isProtected() ? "\0*\0" . $name : $name);
+
+        return new self($reflectionProperty, $key);
+    }
+
+    private function __construct(private ReflectionProperty $reflectionProperty, private string $key)
+    {
+    }
+
+    public function setValue(object $object, mixed $value): void
+    {
+        if (! ($object instanceof InternalProxy && ! $object->__isInitialized())) {
+            $this->reflectionProperty->setRawValue($object, $value);
+
+            return;
+        }
+
+        $object->__setInitialized(true);
+
+        $this->reflectionProperty->setRawValue($object, $value);
+
+        $object->__setInitialized(false);
+    }
+
+    public function getValue(object $object): mixed
+    {
+        return ((array) $object)[$this->key] ?? null;
+    }
+}