[php-src] master: [RFC] Allow Readonly Property Defaults (GH-22588)

NickSdot via GitHub <[email protected]>
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: NickSdot (NickSdot)
Committer: GitHub (web-flow)
Pusher: iluuu1994
Date: 2026-08-11T21:01:17+02:00

Commit: https://github.com/php/php-src/commit/a2640aebc914b3c6fc997c379b1f91a2c4db8c55
Raw diff: https://github.com/php/php-src/commit/a2640aebc914b3c6fc997c379b1f91a2c4db8c55.diff

[RFC] Allow Readonly Property Defaults (GH-22588)

Co-authored-by: DanielEScherzer <[email protected]>

Changed paths:
  A  Zend/tests/readonly_classes/readonly_with_property_default.phpt
  A  Zend/tests/readonly_classes/readonly_with_property_default_trait.phpt
  A  Zend/tests/readonly_props/readonly_with_default_abstract_get_set_implicit_set.phpt
  A  Zend/tests/readonly_props/readonly_with_default_asymmetric_visibility.phpt
  A  Zend/tests/readonly_props/readonly_with_default_inheritance.phpt
  A  Zend/tests/readonly_props/readonly_with_default_interface_get_only.phpt
  A  Zend/tests/readonly_props/readonly_with_default_interface_get_set.phpt
  A  Zend/tests/readonly_props/readonly_with_default_interface_get_set_implicit_set.phpt
  A  Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt
  A  ext/reflection/tests/ReflectionClass_toString_009.phpt
  A  ext/reflection/tests/ReflectionProperty_readonly_with_default.phpt
  A  ext/reflection/tests/ReflectionProperty_toString_002.phpt
  M  NEWS
  M  UPGRADING
  M  Zend/tests/readonly_props/readonly_clone_success1.phpt
  M  Zend/tests/readonly_props/readonly_trait_match.phpt
  M  Zend/tests/readonly_props/readonly_with_default.phpt
  M  Zend/tests/readonly_props/serialization.phpt
  M  Zend/tests/readonly_props/unset.phpt
  M  Zend/zend_compile.c
  M  ext/standard/var_unserializer.re


Diff:

diff --git a/NEWS b/NEWS
index a2122027513f..d9f1281d594f 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,7 @@ PHP                                                                        NEWS
   . Changed run-tests.php to run in parallel by default, using up to 10
     automatically detected workers. Pass -j1 for sequential execution.
     (NickSdot)
+  . Allowed readonly properties to declare default values. (NickSdot)
   . Changed run-tests.php to run test subprocesses without a shell where
     possible. (NickSdot)
   . Fixed GH-23083 (SEGV build_trace_args in zend_exceptions.c with
diff --git a/UPGRADING b/UPGRADING
index 779c95ae9530..f82aad3f8b3c 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -299,6 +299,8 @@ PHP 8.6 UPGRADE NOTES
 ========================================
 
 - Core:
+  . Readonly properties may now declare default values.
+    RFC: https://wiki.php.net/rfc/readonly_property_defaults
   . It is now possible to use reference assign on WeakMap without the key
     needing to be present beforehand.
   . It is now possible to define the __debugInfo() magic method on enums.
diff --git a/Zend/tests/readonly_classes/readonly_with_property_default.phpt b/Zend/tests/readonly_classes/readonly_with_property_default.phpt
new file mode 100644
index 000000000000..49d2bdf03a1e
--- /dev/null
+++ b/Zend/tests/readonly_classes/readonly_with_property_default.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Properties of a readonly class may have default values
+--FILE--
+<?php
+
+readonly class Foo
+{
+    public int $bar = 1;
+    public ?string $nullable = null;
+
+    public function __construct()
+    {
+        try {
+            $this->bar = 2;
+        } catch (Error $e) {
+            echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+        }
+    }
+}
+
+$foo = new Foo();
+var_dump($foo->bar);
+var_dump($foo->nullable);
+
+try {
+    $foo->bar = 3;
+} catch (Error $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+Error: Cannot modify readonly property Foo::$bar
+int(1)
+NULL
+Error: Cannot modify readonly property Foo::$bar
diff --git a/Zend/tests/readonly_classes/readonly_with_property_default_trait.phpt b/Zend/tests/readonly_classes/readonly_with_property_default_trait.phpt
new file mode 100644
index 000000000000..9decd22a8baf
--- /dev/null
+++ b/Zend/tests/readonly_classes/readonly_with_property_default_trait.phpt
@@ -0,0 +1,25 @@
+--TEST--
+Readonly class may use readonly trait property with default value
+--FILE--
+<?php
+
+trait TDefault {
+    public readonly int $prop = 2;
+}
+
+readonly class A {
+    use TDefault;
+}
+
+var_dump(new A()->prop);
+
+class B {
+    use TDefault;
+}
+
+var_dump(new B()->prop);
+
+?>
+--EXPECT--
+int(2)
+int(2)
diff --git a/Zend/tests/readonly_props/readonly_clone_success1.phpt b/Zend/tests/readonly_props/readonly_clone_success1.phpt
index 72cd9e9622b3..50e5e6df81e2 100644
--- a/Zend/tests/readonly_props/readonly_clone_success1.phpt
+++ b/Zend/tests/readonly_props/readonly_clone_success1.phpt
@@ -23,6 +23,41 @@ var_dump($foo2);
 
 var_dump(clone $foo2);
 
+class FooWithDefault {
+    public readonly int $bar = 1;
+
+    public function __clone()
+    {
+        $this->bar++;
+    }
+}
+
+$fooWithDefault = new FooWithDefault();
+
+var_dump(clone $fooWithDefault);
+
+$fooWithDefault2 = clone $fooWithDefault;
+var_dump($fooWithDefault2);
+
+var_dump(clone $fooWithDefault2);
+
+class FooWithDefaultCloneWith {
+    public readonly int $bar = 1;
+
+    public function withBar(int $bar)
+    {
+        return clone($this, ['bar' => $bar]);
+    }
+}
+
+$clone = new FooWithDefaultCloneWith();
+var_dump($clone);
+
+$clone2 = $clone->withBar(2);
+var_dump($clone2);
+
+var_dump($clone2->withBar(0));
+
 ?>
 --EXPECTF--
 object(Foo)#%d (%d) {
@@ -37,3 +72,27 @@ object(Foo)#%d (%d) {
   ["bar"]=>
   int(3)
 }
+object(FooWithDefault)#%d (%d) {
+  ["bar"]=>
+  int(2)
+}
+object(FooWithDefault)#%d (%d) {
+  ["bar"]=>
+  int(2)
+}
+object(FooWithDefault)#%d (%d) {
+  ["bar"]=>
+  int(3)
+}
+object(FooWithDefaultCloneWith)#%d (%d) {
+  ["bar"]=>
+  int(1)
+}
+object(FooWithDefaultCloneWith)#%d (%d) {
+  ["bar"]=>
+  int(2)
+}
+object(FooWithDefaultCloneWith)#%d (%d) {
+  ["bar"]=>
+  int(0)
+}
diff --git a/Zend/tests/readonly_props/readonly_trait_match.phpt b/Zend/tests/readonly_props/readonly_trait_match.phpt
index 00aa6349aa14..36f2e11ec2bc 100644
--- a/Zend/tests/readonly_props/readonly_trait_match.phpt
+++ b/Zend/tests/readonly_props/readonly_trait_match.phpt
@@ -13,7 +13,20 @@ class C {
     use T1, T2;
 }
 
+trait TDefault1 {
+    public readonly int $prop = 1;
+}
+trait TDefault2 {
+    public readonly int $prop = 1;
+}
+class CDefault {
+    use TDefault1, TDefault2;
+}
+
+var_dump(new CDefault()->prop);
+
 ?>
 ===DONE===
 --EXPECT--
+int(1)
 ===DONE===
diff --git a/Zend/tests/readonly_props/readonly_with_default.phpt b/Zend/tests/readonly_props/readonly_with_default.phpt
index 12afe5cde153..affe62f8d1ff 100644
--- a/Zend/tests/readonly_props/readonly_with_default.phpt
+++ b/Zend/tests/readonly_props/readonly_with_default.phpt
@@ -3,17 +3,43 @@ Readonly property with default value
 --FILE--
 <?php
 
+enum E {
+    case Case;
+}
+
 class Test {
     public readonly int $prop = 1;
+    public readonly string $className = self::class;
+    public readonly ?string $nullable = null;
+    public readonly array $array = [1, "two" => 2];
+    public readonly E $enum = E::Case;
+    public readonly string $enumString = E::Case->name;
 }
 
 $test = new Test;
+var_dump($test->prop);
+var_dump($test->className);
+var_dump($test->nullable);
+var_dump($test->array);
+var_dump($test->enum);
+var_dump($test->enumString);
 try {
     $test->prop = 2;
 } catch (Error $e) {
-    echo $e->getMessage(), "\n";
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
 }
 
 ?>
---EXPECTF--
-Fatal error: Readonly property Test::$prop cannot have default value in %s on line %d
+--EXPECT--
+int(1)
+string(4) "Test"
+NULL
+array(2) {
+  [0]=>
+  int(1)
+  ["two"]=>
+  int(2)
+}
+enum(E::Case)
+string(4) "Case"
+Error: Cannot modify readonly property Test::$prop
diff --git a/Zend/tests/readonly_props/readonly_with_default_abstract_get_set_implicit_set.phpt b/Zend/tests/readonly_props/readonly_with_default_abstract_get_set_implicit_set.phpt
new file mode 100644
index 000000000000..e61574d07aaf
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_abstract_get_set_implicit_set.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Readonly property with default value has restricted set visibility for get/set abstract property
+--DESCRIPTION--
+The error message should be improved, the set access level comes from readonly.
+--FILE--
+<?php
+
+abstract class P {
+    public abstract int $prop { get; set; }
+}
+
+class C extends P {
+    public readonly int $prop = 42;
+}
+
+?>
+--EXPECTF--
+Fatal error: Set access level of C::$prop must be omitted (as in class P) in %s on line %d
diff --git a/Zend/tests/readonly_props/readonly_with_default_asymmetric_visibility.phpt b/Zend/tests/readonly_props/readonly_with_default_asymmetric_visibility.phpt
new file mode 100644
index 000000000000..11b5a741ca17
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_asymmetric_visibility.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Readonly property with default value and asymmetric visibility
+--FILE--
+<?php
+
+class Test {
+    public readonly int $default = 1;
+    public private(set) readonly int $private = 2;
+    public protected(set) readonly int $protected = 3;
+    public public(set) readonly int $public = 4;
+}
+
+$test = new Test();
+
+foreach (['default', 'private', 'protected', 'public'] as $prop) {
+    $before = $test->$prop;
+    try {
+        $test->$prop = 42;
+    } catch (Error $e) {
+        echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+    }
+    echo "$$prop before $before, after {$test->$prop}", PHP_EOL;
+}
+
+?>
+--EXPECT--
+Error: Cannot modify readonly property Test::$default
+$default before 1, after 1
+Error: Cannot modify readonly property Test::$private
+$private before 2, after 2
+Error: Cannot modify readonly property Test::$protected
+$protected before 3, after 3
+Error: Cannot modify readonly property Test::$public
+$public before 4, after 4
diff --git a/Zend/tests/readonly_props/readonly_with_default_inheritance.phpt b/Zend/tests/readonly_props/readonly_with_default_inheritance.phpt
new file mode 100644
index 000000000000..8b285dcdd769
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_inheritance.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Readonly property with default value and inheritance
+--FILE--
+<?php
+
+class ParentDefault {
+    public readonly int $prop = 1;
+}
+
+class ChildInherits extends ParentDefault {}
+
+class ChildOverrides extends ParentDefault {
+    public readonly int $prop = 2;
+}
+
+class PrivateParent {
+    private readonly int $prop = 3;
+
+    public function getParentProp(): int {
+        return $this->prop;
+    }
+}
+
+class PrivateChild extends PrivateParent {
+    public readonly int $prop = 4;
+}
+
+var_dump(new ChildInherits()->prop);
+var_dump(new ChildOverrides()->prop);
+
+$privateChild = new PrivateChild();
+var_dump($privateChild->getParentProp());
+var_dump($privateChild->prop);
+
+?>
+--EXPECT--
+int(1)
+int(2)
+int(3)
+int(4)
diff --git a/Zend/tests/readonly_props/readonly_with_default_interface_get_only.phpt b/Zend/tests/readonly_props/readonly_with_default_interface_get_only.phpt
new file mode 100644
index 000000000000..1e64e26b4517
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_interface_get_only.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Readonly property with default value satisfies get-only interface property
+--FILE--
+<?php
+
+interface I {
+    public int $prop { get; }
+}
+
+class C implements I {
+    public readonly int $prop = 42;
+}
+
+var_dump(new C()->prop);
+?>
+--EXPECT--
+int(42)
diff --git a/Zend/tests/readonly_props/readonly_with_default_interface_get_set.phpt b/Zend/tests/readonly_props/readonly_with_default_interface_get_set.phpt
new file mode 100644
index 000000000000..102444cc044f
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_interface_get_set.phpt
@@ -0,0 +1,16 @@
+--TEST--
+Readonly public(set) property with default value does not satisfy get/set interface property
+--FILE--
+<?php
+
+interface I {
+    public int $prop { get; set; }
+}
+
+// does not satisfy set
+class C implements I {
+    public public(set) readonly int $prop = 42;
+}
+?>
+--EXPECTF--
+Fatal error: Class C contains 1 abstract method and must therefore be declared abstract or implement the remaining method (I::$prop::set) in %s on line %d
diff --git a/Zend/tests/readonly_props/readonly_with_default_interface_get_set_implicit_set.phpt b/Zend/tests/readonly_props/readonly_with_default_interface_get_set_implicit_set.phpt
new file mode 100644
index 000000000000..106bf2aeb0cf
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_interface_get_set_implicit_set.phpt
@@ -0,0 +1,17 @@
+--TEST--
+Readonly property with default value has restricted set visibility for get/set interface property
+--DESCRIPTION--
+The error message should be improved, the set access level comes from readonly. Ref: Zend/tests/property_hooks/interface_get_set_readonly.phpt
+--FILE--
+<?php
+
+interface I {
+    public int $prop { get; set; }
+}
+
+class C implements I {
+    public readonly int $prop = 42;
+}
+?>
+--EXPECTF--
+Fatal error: Set access level of C::$prop must be omitted (as in class I) in %s on line %d
diff --git a/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt b/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt
new file mode 100644
index 000000000000..6668b1d860bb
--- /dev/null
+++ b/Zend/tests/readonly_props/readonly_with_default_trait_mismatch.phpt
@@ -0,0 +1,20 @@
+--TEST--
+Readonly trait property default value mismatch
+--FILE--
+<?php
+
+trait T1 {
+    public readonly int $prop = 1;
+}
+
+trait T2 {
+    public readonly int $prop = 2;
+}
+
+class C {
+    use T1, T2;
+}
+
+?>
+--EXPECTF--
+Fatal error: T1 and T2 define the same property ($prop) in the composition of C. However, the definition differs and is considered incompatible. Class was composed in %s on line %d
diff --git a/Zend/tests/readonly_props/serialization.phpt b/Zend/tests/readonly_props/serialization.phpt
index f9e1f364673f..0d56067760d5 100644
--- a/Zend/tests/readonly_props/serialization.phpt
+++ b/Zend/tests/readonly_props/serialization.phpt
@@ -17,6 +17,42 @@ var_dump(unserialize($s));
 var_dump(unserialize("O:4:\"Test\":1:{s:4:\"prop\";i:2;}"));
 var_dump(unserialize("O:4:\"Test\":2:{s:4:\"prop\";i:2;s:4:\"prop\";i:3;}"));
 
+class TestDefault {
+    public readonly int $prop = 1;
+}
+
+var_dump($s = serialize(new TestDefault));
+var_dump(unserialize($s));
+
+var_dump(unserialize("O:11:\"TestDefault\":0:{}"));
+var_dump(unserialize("O:11:\"TestDefault\":1:{s:4:\"prop\";i:2;}"));
+
+class TestDefaultWithUnserialize {
+    public readonly int $prop = 1;
+    public public(set) readonly int $lock = 1;
+
+    public function __unserialize(array $data): void {
+        foreach ($data as $key => $value) {
+            $this->{$key} = $value;
+        }
+    }
+}
+
+$testDefaultWithUnserialize = unserialize("O:26:\"TestDefaultWithUnserialize\":1:{s:4:\"prop\";i:2;}");
+var_dump($testDefaultWithUnserialize);
+
+try {
+    $testDefaultWithUnserialize->prop = 3;
+} catch (Error $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+try {
+    $testDefaultWithUnserialize->lock = 3;
+} catch (Error $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
 ?>
 --EXPECT--
 string(30) "O:4:"Test":1:{s:4:"prop";i:1;}"
@@ -32,3 +68,24 @@ object(Test)#1 (1) {
   ["prop"]=>
   int(3)
 }
+string(38) "O:11:"TestDefault":1:{s:4:"prop";i:1;}"
+object(TestDefault)#1 (1) {
+  ["prop"]=>
+  int(1)
+}
+object(TestDefault)#1 (1) {
+  ["prop"]=>
+  int(1)
+}
+object(TestDefault)#1 (1) {
+  ["prop"]=>
+  int(2)
+}
+object(TestDefaultWithUnserialize)#1 (2) {
+  ["prop"]=>
+  int(2)
+  ["lock"]=>
+  int(1)
+}
+Error: Cannot modify readonly property TestDefaultWithUnserialize::$prop
+Error: Cannot modify readonly property TestDefaultWithUnserialize::$lock
diff --git a/Zend/tests/readonly_props/unset.phpt b/Zend/tests/readonly_props/unset.phpt
index b8bd4218fa0c..823c021f6122 100644
--- a/Zend/tests/readonly_props/unset.phpt
+++ b/Zend/tests/readonly_props/unset.phpt
@@ -54,6 +54,31 @@ try {
     echo $e->getMessage(), "\n";
 }
 
+class Test4 {
+    public readonly int $prop = 1;
+
+    public function __construct() {
+        try {
+            unset($this->prop);
+        } catch (Error $e) {
+            echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+        }
+    }
+
+    public function __get($name) {
+        throw new Exception('Unreachable');
+    }
+}
+
+$test = new Test4;
+var_dump($test->prop); // Don't call __get.
+try {
+    unset($test->prop);
+} catch (Error $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+var_dump($test->prop); // Still don't call __get.
+
 ?>
 --EXPECT--
 Cannot unset readonly property Test::$prop
@@ -62,3 +87,7 @@ int(1)
 int(1)
 Cannot unset readonly property Test2::$prop
 Cannot unset protected(set) readonly property Test3::$prop from global scope
+Error: Cannot unset readonly property Test4::$prop
+int(1)
+Error: Cannot unset readonly property Test4::$prop
+int(1)
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 6fd4df023520..1d837c59832b 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -9497,11 +9497,6 @@ static void zend_compile_prop_decl(zend_ast *ast, zend_ast *type_ast, uint32_t f
 				zend_error_noreturn(E_COMPILE_ERROR, "Readonly property %s::$%s must have type",
 					ZSTR_VAL(ce->name), ZSTR_VAL(name));
 			}
-			if (!Z_ISUNDEF(value_zv)) {
-				zend_error_noreturn(E_COMPILE_ERROR,
-					"Readonly property %s::$%s cannot have default value",
-					ZSTR_VAL(ce->name), ZSTR_VAL(name));
-			}
 			if (flags & ZEND_ACC_STATIC) {
 				zend_error_noreturn(E_COMPILE_ERROR,
 					"Static property %s::$%s cannot be readonly",
diff --git a/ext/reflection/tests/ReflectionClass_toString_009.phpt b/ext/reflection/tests/ReflectionClass_toString_009.phpt
new file mode 100644
index 000000000000..b2d118c459b8
--- /dev/null
+++ b/ext/reflection/tests/ReflectionClass_toString_009.phpt
@@ -0,0 +1,32 @@
+--TEST--
+ReflectionClass::__toString() - readonly property with default
+--FILE--
+<?php
+
+class Test {
+    public readonly int $property = 42;
+}
+
+echo new ReflectionClass(Test::class);
+
+?>
+--EXPECTF--
+Class [ <user> class Test ] {
+  @@ %s 3-5
+
+  - Constants [0] {
+  }
+
+  - Static properties [0] {
+  }
+
+  - Static methods [0] {
+  }
+
+  - Properties [1] {
+    Property [ public protected(set) readonly int $property = 42 ]
+  }
+
+  - Methods [0] {
+  }
+}
diff --git a/ext/reflection/tests/ReflectionProperty_readonly_with_default.phpt b/ext/reflection/tests/ReflectionProperty_readonly_with_default.phpt
new file mode 100644
index 000000000000..d3c3e8a084e4
--- /dev/null
+++ b/ext/reflection/tests/ReflectionProperty_readonly_with_default.phpt
@@ -0,0 +1,35 @@
+--TEST--
+Reflection for readonly property with default value
+--FILE--
+<?php
+
+class Foo {
+    public readonly int $prop = 1;
+    public readonly ?string $nullable = null;
+}
+
+$rp = new ReflectionProperty(Foo::class, 'prop');
+var_dump($rp->isReadOnly());
+var_dump($rp->hasDefaultValue());
+var_dump($rp->getDefaultValue());
+var_dump(new ReflectionClass(Foo::class)->getDefaultProperties());
+
+$test = new Foo();
+try {
+    $rp->setValue($test, 2);
+} catch (Error $e) {
+    echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+
+?>
+--EXPECT--
+bool(true)
+bool(true)
+int(1)
+array(2) {
+  ["prop"]=>
+  int(1)
+  ["nullable"]=>
+  NULL
+}
+Error: Cannot modify readonly property Foo::$prop
diff --git a/ext/reflection/tests/ReflectionProperty_toString_002.phpt b/ext/reflection/tests/ReflectionProperty_toString_002.phpt
new file mode 100644
index 000000000000..5b93d2149b9a
--- /dev/null
+++ b/ext/reflection/tests/ReflectionProperty_toString_002.phpt
@@ -0,0 +1,14 @@
+--TEST--
+ReflectionProperty::__toString() - readonly with default
+--FILE--
+<?php
+
+class Test {
+    public readonly int $nick = 42;
+}
+
+echo new ReflectionProperty(Test::class, 'nick');
+
+?>
+--EXPECT--
+Property [ public protected(set) readonly int $nick = 42 ]
diff --git a/ext/standard/var_unserializer.re b/ext/standard/var_unserializer.re
index eca9660c5605..1fb8793c2bbc 100644
--- a/ext/standard/var_unserializer.re
+++ b/ext/standard/var_unserializer.re
@@ -17,6 +17,7 @@
 #include "php_incomplete_class.h"
 #include "zend_portability.h"
 #include "zend_exceptions.h"
+#include "zend_objects.h"
 
 /* {{{ reference-handling for unserializer: var_* */
 #define VAR_ENTRIES_MAX 1018     /* 1024 - offsetof(php_unserialize_data, entries) / sizeof(void*) */
@@ -300,6 +301,7 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
 					zval param;
 					ZVAL_COPY(&param, &var_dtor_hash->data[i + 1]);
 
+					zend_object_set_properties_reinitable(Z_OBJ_P(zv), /* reinitable */ true);
 					BG(serialize_lock)++;
 					zend_call_known_instance_method_with_1_params(
 						Z_OBJCE_P(zv)->__unserialize, Z_OBJ_P(zv), NULL, &param);
@@ -308,6 +310,7 @@ PHPAPI void var_destroy(php_unserialize_data_t *var_hashx)
 						GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
 					}
 					BG(serialize_lock)--;
+					zend_object_set_properties_reinitable(Z_OBJ_P(zv), /* reinitable */ false);
 					zval_ptr_dtor(&param);
 				} else {
 					GC_ADD_FLAGS(Z_OBJ_P(zv), IS_OBJ_DESTRUCTOR_CALLED);
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.