diff --git a/Zend/zend.c b/Zend/zend.c
index 8c3351e177f22..318b8b8b1a40d 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -546,6 +546,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
 				HashTable *properties;
 
 				zend_object *zobj = Z_OBJ_P(expr);
+				uint32_t *guard = zend_get_recursion_guard(zobj);
 				zend_string *class_name = Z_OBJ_HANDLER_P(expr, get_class_name)(zobj);
 				smart_str_appends(buf, ZSTR_VAL(class_name));
 				zend_string_release_ex(class_name, 0);
@@ -561,7 +562,7 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
 					smart_str_appendc(buf, '\n');
 				}
 
-				if (GC_IS_RECURSIVE(Z_OBJ_P(expr))) {
+				if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) {
 					smart_str_appends(buf, " *RECURSION*");
 					return;
 				}
@@ -571,9 +572,9 @@ static void zend_print_zval_r_to_buf(smart_str *buf, zval *expr, int indent) /*
 					break;
 				}
 
-				GC_PROTECT_RECURSION(Z_OBJ_P(expr));
+				ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj);
 				print_hash(buf, properties, indent, 1);
-				GC_UNPROTECT_RECURSION(Z_OBJ_P(expr));
+				ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj);
 
 				zend_release_properties(properties);
 				break;
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index d0b863335e291..d629b7166a58b 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2746,6 +2746,7 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z
 		ce->__tostring = fptr;
 	} else if (zend_string_equals_literal(lcname, ZEND_DEBUGINFO_FUNC_NAME)) {
 		ce->__debugInfo = fptr;
+		ce->ce_flags |= ZEND_ACC_USE_GUARDS;
 	} else if (zend_string_equals_literal(lcname, "__serialize")) {
 		ce->__serialize = fptr;
 	} else if (zend_string_equals_literal(lcname, "__unserialize")) {
diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c
index b18d0f46f80a6..8985bb9edb5e4 100644
--- a/Zend/zend_object_handlers.c
+++ b/Zend/zend_object_handlers.c
@@ -36,10 +36,10 @@
 #define ZEND_WRONG_PROPERTY_OFFSET   0
 
 /* guard flags */
-#define IN_GET		(1<<0)
-#define IN_SET		(1<<1)
-#define IN_UNSET	(1<<2)
-#define IN_ISSET	(1<<3)
+#define IN_GET		ZEND_GUARD_PROPERTY_GET
+#define IN_SET		ZEND_GUARD_PROPERTY_SET
+#define IN_UNSET	ZEND_GUARD_PROPERTY_UNSET
+#define IN_ISSET	ZEND_GUARD_PROPERTY_ISSET
 
 /*
   __X accessors explanation:
@@ -542,30 +542,36 @@ static void zend_property_guard_dtor(zval *el) /* {{{ */ {
 }
 /* }}} */
 
+static zend_always_inline zval *zend_get_guard_value(zend_object *zobj)
+{
+	return zobj->properties_table + zobj->ce->default_properties_count;
+}
+
 ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member) /* {{{ */
 {
 	HashTable *guards;
 	zval *zv;
 	uint32_t *ptr;
 
+
 	ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS);
-	zv = zobj->properties_table + zobj->ce->default_properties_count;
+	zv = zend_get_guard_value(zobj);
 	if (EXPECTED(Z_TYPE_P(zv) == IS_STRING)) {
 		zend_string *str = Z_STR_P(zv);
 		if (EXPECTED(str == member) ||
 		    /* str and member don't necessarily have a pre-calculated hash value here */
 		    EXPECTED(zend_string_equal_content(str, member))) {
-			return &Z_PROPERTY_GUARD_P(zv);
-		} else if (EXPECTED(Z_PROPERTY_GUARD_P(zv) == 0)) {
+			return &Z_GUARD_P(zv);
+		} else if (EXPECTED(Z_GUARD_P(zv) == 0)) {
 			zval_ptr_dtor_str(zv);
 			ZVAL_STR_COPY(zv, member);
-			return &Z_PROPERTY_GUARD_P(zv);
+			return &Z_GUARD_P(zv);
 		} else {
 			ALLOC_HASHTABLE(guards);
 			zend_hash_init(guards, 8, NULL, zend_property_guard_dtor, 0);
 			/* mark pointer as "special" using low bit */
 			zend_hash_add_new_ptr(guards, str,
-				(void*)(((uintptr_t)&Z_PROPERTY_GUARD_P(zv)) | 1));
+				(void*)(((uintptr_t)&Z_GUARD_P(zv)) | 1));
 			zval_ptr_dtor_str(zv);
 			ZVAL_ARR(zv, guards);
 		}
@@ -579,8 +585,8 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe
 	} else {
 		ZEND_ASSERT(Z_TYPE_P(zv) == IS_UNDEF);
 		ZVAL_STR_COPY(zv, member);
-		Z_PROPERTY_GUARD_P(zv) = 0;
-		return &Z_PROPERTY_GUARD_P(zv);
+		Z_GUARD_P(zv) &= ~ZEND_GUARD_PROPERTY_MASK;
+		return &Z_GUARD_P(zv);
 	}
 	/* we have to allocate uint32_t separately because ht->arData may be reallocated */
 	ptr = (uint32_t*)emalloc(sizeof(uint32_t));
@@ -589,6 +595,15 @@ ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *membe
 }
 /* }}} */
 
+ZEND_API uint32_t *zend_get_recursion_guard(zend_object *zobj)
+{
+	if (!(zobj->ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
+		return NULL;
+	}
+	zval *zv = zend_get_guard_value(zobj);
+	return &Z_GUARD_P(zv);
+}
+
 ZEND_API zval *zend_std_read_property(zend_object *zobj, zend_string *name, int type, void **cache_slot, zval *rv) /* {{{ */
 {
 	zval *retval;
diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h
index 475ba6263825d..23a6c18241603 100644
--- a/Zend/zend_object_handlers.h
+++ b/Zend/zend_object_handlers.h
@@ -241,6 +241,10 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce
 
 ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member);
 
+ZEND_API uint32_t *zend_get_property_guard(zend_object *zobj, zend_string *member);
+
+ZEND_API uint32_t *zend_get_recursion_guard(zend_object *zobj);
+
 /* Default behavior for get_properties_for. For use as a fallback in custom
  * get_properties_for implementations. */
 ZEND_API HashTable *zend_std_get_properties_for(zend_object *obj, zend_prop_purpose purpose);
diff --git a/Zend/zend_objects.c b/Zend/zend_objects.c
index 4d40b76854df0..4c4b3cf30c13d 100644
--- a/Zend/zend_objects.c
+++ b/Zend/zend_objects.c
@@ -35,7 +35,9 @@ static zend_always_inline void _zend_object_std_init(zend_object *object, zend_c
 	object->properties = NULL;
 	zend_objects_store_put(object);
 	if (UNEXPECTED(ce->ce_flags & ZEND_ACC_USE_GUARDS)) {
-		ZVAL_UNDEF(object->properties_table + object->ce->default_properties_count);
+		zval *guard_value = object->properties_table + object->ce->default_properties_count;
+		ZVAL_UNDEF(guard_value);
+		Z_GUARD_P(guard_value) = 0;
 	}
 }
 
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index bf4fd9d18a41a..a7d12a3af8abf 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -345,7 +345,7 @@ struct _zval_struct {
 		uint32_t     num_args;             /* arguments number for EX(This) */
 		uint32_t     fe_pos;               /* foreach position */
 		uint32_t     fe_iter_idx;          /* foreach iterator index */
-		uint32_t     property_guard;       /* single property guard */
+		uint32_t     guard;                /* recursion and single property guard */
 		uint32_t     constant_flags;       /* constant flags */
 		uint32_t     extra;                /* not further specified */
 	} u2;
@@ -619,6 +619,22 @@ struct _zend_ast_ref {
 #define _IS_BOOL					18
 #define _IS_NUMBER					19
 
+/* guard flags */
+#define ZEND_GUARD_PROPERTY_GET		(1<<0)
+#define ZEND_GUARD_PROPERTY_SET		(1<<1)
+#define ZEND_GUARD_PROPERTY_UNSET	(1<<2)
+#define ZEND_GUARD_PROPERTY_ISSET	(1<<3)
+#define ZEND_GUARD_PROPERTY_MASK	15
+#define ZEND_GUARD_RECURSION_DEBUG	(1<<4)
+#define ZEND_GUARD_RECURSION_EXPORT	(1<<5)
+#define ZEND_GUARD_RECURSION_JSON	(1<<6)
+
+#define ZEND_GUARD_RECURSION_TYPE(t) ZEND_GUARD_RECURSION_ ## t
+
+#define ZEND_GUARD_IS_RECURSIVE(pg, t)			((*pg & ZEND_GUARD_RECURSION_TYPE(t)) != 0)
+#define ZEND_GUARD_PROTECT_RECURSION(pg, t)		*pg |= ZEND_GUARD_RECURSION_TYPE(t)
+#define ZEND_GUARD_UNPROTECT_RECURSION(pg, t)	*pg &= ~ZEND_GUARD_RECURSION_TYPE(t)
+
 static zend_always_inline uint8_t zval_get_type(const zval* pz) {
 	return pz->u1.v.type;
 }
@@ -659,8 +675,8 @@ static zend_always_inline uint8_t zval_get_type(const zval* pz) {
 #define Z_FE_ITER(zval)				(zval).u2.fe_iter_idx
 #define Z_FE_ITER_P(zval_p)			Z_FE_ITER(*(zval_p))
 
-#define Z_PROPERTY_GUARD(zval)		(zval).u2.property_guard
-#define Z_PROPERTY_GUARD_P(zval_p)	Z_PROPERTY_GUARD(*(zval_p))
+#define Z_GUARD(zval)				(zval).u2.guard
+#define Z_GUARD_P(zval_p)			Z_GUARD(*(zval_p))
 
 #define Z_CONSTANT_FLAGS(zval)		(zval).u2.constant_flags
 #define Z_CONSTANT_FLAGS_P(zval_p)	Z_CONSTANT_FLAGS(*(zval_p))
@@ -859,6 +875,25 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) {
 #define Z_PROTECT_RECURSION_P(zv)   Z_PROTECT_RECURSION(*(zv))
 #define Z_UNPROTECT_RECURSION_P(zv) Z_UNPROTECT_RECURSION(*(zv))
 
+#define ZEND_GUARD_OR_GC_IS_RECURSIVE(pg, t, zobj) \
+	(pg ? ZEND_GUARD_IS_RECURSIVE(pg, t) : GC_IS_RECURSIVE(zobj))
+
+#define ZEND_GUARD_OR_GC_PROTECT_RECURSION(pg, t, zobj) do { \
+		if (pg) { \
+			ZEND_GUARD_PROTECT_RECURSION(pg, t); \
+		} else { \
+			GC_PROTECT_RECURSION(zobj); \
+		} \
+	} while(0)
+
+#define ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(pg, t, zobj) do { \
+		if (pg) { \
+			ZEND_GUARD_UNPROTECT_RECURSION(pg, t); \
+		} else { \
+			GC_UNPROTECT_RECURSION(zobj); \
+		} \
+	} while(0)
+
 /* All data types < IS_STRING have their constructor/destructors skipped */
 #define Z_CONSTANT(zval)			(Z_TYPE(zval) == IS_CONSTANT_AST)
 #define Z_CONSTANT_P(zval_p)		Z_CONSTANT(*(zval_p))
diff --git a/ext/json/json.c b/ext/json/json.c
index d102edebb23a4..31ed76703ec12 100644
--- a/ext/json/json.c
+++ b/ext/json/json.c
@@ -37,10 +37,17 @@ PHP_JSON_API zend_class_entry *php_json_exception_ce;
 
 PHP_JSON_API ZEND_DECLARE_MODULE_GLOBALS(json)
 
+static int php_json_implement_json_serializable(zend_class_entry *interface, zend_class_entry *class_type)
+{
+	class_type->ce_flags |= ZEND_ACC_USE_GUARDS;
+	return SUCCESS;
+}
+
 /* {{{ MINIT */
 static PHP_MINIT_FUNCTION(json)
 {
 	php_json_serializable_ce = register_class_JsonSerializable();
+	php_json_serializable_ce->interface_gets_implemented = php_json_implement_json_serializable;
 
 	php_json_exception_ce = register_class_JsonException(zend_ce_exception);
 
diff --git a/ext/json/json_encoder.c b/ext/json/json_encoder.c
index 14fd86d73426c..4709c0e2be4a7 100644
--- a/ext/json/json_encoder.c
+++ b/ext/json/json_encoder.c
@@ -531,11 +531,14 @@ zend_result php_json_escape_string(
 static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val, int options, php_json_encoder *encoder) /* {{{ */
 {
 	zend_class_entry *ce = Z_OBJCE_P(val);
-	HashTable* myht = Z_OBJPROP_P(val);
+	zend_object *obj = Z_OBJ_P(val);
+	uint32_t *guard = zend_get_recursion_guard(obj);
 	zval retval, fname;
 	zend_result return_code;
 
-	if (myht && GC_IS_RECURSIVE(myht)) {
+	ZEND_ASSERT(guard != NULL);
+
+	if (ZEND_GUARD_IS_RECURSIVE(guard, JSON)) {
 		encoder->error_code = PHP_JSON_ERROR_RECURSION;
 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
 			smart_str_appendl(buf, "null", 4);
@@ -543,7 +546,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val
 		return FAILURE;
 	}
 
-	PHP_JSON_HASH_PROTECT_RECURSION(myht);
+	ZEND_GUARD_PROTECT_RECURSION(guard, JSON);
 
 	ZVAL_STRING(&fname, "jsonSerialize");
 
@@ -556,7 +559,7 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val
 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
 			smart_str_appendl(buf, "null", 4);
 		}
-		PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
+		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
 		return FAILURE;
 	}
 
@@ -568,19 +571,19 @@ static zend_result php_json_encode_serializable_object(smart_str *buf, zval *val
 		if (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR) {
 			smart_str_appendl(buf, "null", 4);
 		}
-		PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
+		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
 		return FAILURE;
 	}
 
 	if ((Z_TYPE(retval) == IS_OBJECT) &&
 		(Z_OBJ(retval) == Z_OBJ_P(val))) {
 		/* Handle the case where jsonSerialize does: return $this; by going straight to encode array */
-		PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
+		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
 		return_code = php_json_encode_array(buf, &retval, options, encoder);
 	} else {
 		/* All other types, encode as normal */
 		return_code = php_json_encode_zval(buf, &retval, options, encoder);
-		PHP_JSON_HASH_UNPROTECT_RECURSION(myht);
+		ZEND_GUARD_UNPROTECT_RECURSION(guard, JSON);
 	}
 
 	zval_ptr_dtor(&retval);
diff --git a/ext/json/tests/json_encode_recursion_01.phpt b/ext/json/tests/json_encode_recursion_01.phpt
new file mode 100644
index 0000000000000..f1bbf9155bc71
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_01.phpt
@@ -0,0 +1,26 @@
+--TEST--
+json_encode() Recursion test with just JsonSerializable
+--FILE--
+<?php
+
+class SerializingTest implements JsonSerializable
+{
+    public $a = 1;
+
+    private $b = 'hide';
+
+    protected $c = 'protect';
+
+    public function jsonSerialize(): mixed
+    {
+        $result = json_encode($this);
+        var_dump($result);
+        return $this;
+    }
+}
+
+var_dump(json_encode(new SerializingTest()));
+?>
+--EXPECT--
+bool(false)
+string(7) "{"a":1}"
diff --git a/ext/json/tests/json_encode_recursion_02.phpt b/ext/json/tests/json_encode_recursion_02.phpt
new file mode 100644
index 0000000000000..aa525f5a30717
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_02.phpt
@@ -0,0 +1,25 @@
+--TEST--
+json_encode() Recursion test with JsonSerializable and var_dump simple
+--FILE--
+<?php
+
+class SerializingTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function jsonSerialize(): mixed
+    {
+        var_dump($this);
+        return $this;
+    }
+}
+
+var_dump(json_encode(new SerializingTest()));
+
+?>
+--EXPECT--
+object(SerializingTest)#1 (1) {
+  ["a"]=>
+  int(1)
+}
+string(7) "{"a":1}"
diff --git a/ext/json/tests/json_encode_recursion_03.phpt b/ext/json/tests/json_encode_recursion_03.phpt
new file mode 100644
index 0000000000000..dac0b0ea313eb
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_03.phpt
@@ -0,0 +1,38 @@
+--TEST--
+json_encode() Recursion test with JsonSerializable and __debugInfo
+--FILE--
+<?php
+
+class SerializingTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function __debugInfo()
+    {
+        return [ 'result' => json_encode($this) ];
+    }
+
+    public function jsonSerialize(): mixed
+    {
+        var_dump($this);
+        return $this;
+    }
+}
+
+var_dump(json_encode(new SerializingTest()));
+echo "---------\n";
+var_dump(new SerializingTest());
+
+?>
+--EXPECT--
+object(SerializingTest)#1 (1) {
+  ["result"]=>
+  bool(false)
+}
+string(7) "{"a":1}"
+---------
+*RECURSION*
+object(SerializingTest)#1 (1) {
+  ["result"]=>
+  string(7) "{"a":1}"
+}
diff --git a/ext/json/tests/json_encode_recursion_04.phpt b/ext/json/tests/json_encode_recursion_04.phpt
new file mode 100644
index 0000000000000..1dde4e4f4a095
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_04.phpt
@@ -0,0 +1,41 @@
+--TEST--
+json_encode() Recursion test with JsonSerializable, __debugInfo and var_export
+--FILE--
+<?php
+
+class SerializingTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function __debugInfo()
+    {
+        return [ 'result' => var_export($this, true) ];
+    }
+
+    public function jsonSerialize(): mixed
+    {
+        var_dump($this);
+        return $this;
+    }
+}
+
+var_dump(json_encode(new SerializingTest()));
+echo "---------\n";
+var_dump(new SerializingTest());
+
+?>
+--EXPECT--
+object(SerializingTest)#1 (1) {
+  ["result"]=>
+  string(52) "\SerializingTest::__set_state(array(
+   'a' => 1,
+))"
+}
+string(7) "{"a":1}"
+---------
+object(SerializingTest)#1 (1) {
+  ["result"]=>
+  string(52) "\SerializingTest::__set_state(array(
+   'a' => 1,
+))"
+}
diff --git a/ext/json/tests/json_encode_recursion_05.phpt b/ext/json/tests/json_encode_recursion_05.phpt
new file mode 100644
index 0000000000000..3cbc2000d32e9
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_05.phpt
@@ -0,0 +1,37 @@
+--TEST--
+json_encode() Recursion test with JsonSerializable, __debugInfo and print_r
+--FILE--
+<?php
+
+class SerializingTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function __debugInfo()
+    {
+        return [ 'result' => $this->a ];
+    }
+
+    public function jsonSerialize(): mixed
+    {
+        print_r($this);
+        return $this;
+    }
+}
+
+var_dump(json_encode(new SerializingTest()));
+echo "---------\n";
+var_dump(new SerializingTest());
+
+?>
+--EXPECT--
+SerializingTest Object
+(
+    [result] => 1
+)
+string(7) "{"a":1}"
+---------
+object(SerializingTest)#1 (1) {
+  ["result"]=>
+  int(1)
+}
diff --git a/ext/json/tests/json_encode_recursion_06.phpt b/ext/json/tests/json_encode_recursion_06.phpt
new file mode 100644
index 0000000000000..d8e6fea226e6f
--- /dev/null
+++ b/ext/json/tests/json_encode_recursion_06.phpt
@@ -0,0 +1,41 @@
+--TEST--
+json_encode() Recursion test with JsonSerializable and serialize
+--FILE--
+<?php
+
+class JsonEncodeFirstTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function __serialize()
+    {
+        return [ 'result' => $this->a ];
+    }
+
+    public function jsonSerialize(): mixed
+    {
+        return [ 'serialize' => serialize($this) ];
+    }
+}
+
+class SerializeFirstTest implements JsonSerializable
+{
+    public $a = 1;
+
+    public function __serialize()
+    {
+        return [ 'result' => json_encode($this) ];
+    }
+
+    public function jsonSerialize(): mixed
+    {
+        return [ 'json' => serialize($this) ];
+    }
+}
+
+var_dump(json_encode(new JsonEncodeFirstTest()));
+var_dump(serialize(new SerializeFirstTest()));
+?>
+--EXPECT--
+string(68) "{"serialize":"O:19:\"JsonEncodeFirstTest\":1:{s:6:\"result\";i:1;}"}"
+string(113) "O:18:"SerializeFirstTest":1:{s:6:"result";s:62:"{"json":"O:18:\"SerializeFirstTest\":1:{s:6:\"result\";b:0;}"}";}"
diff --git a/ext/standard/var.c b/ext/standard/var.c
index 7b3fcbc6ae3c2..4f04ff6c0deb3 100644
--- a/ext/standard/var.c
+++ b/ext/standard/var.c
@@ -153,12 +153,13 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */
 				php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval));
 				return;
 			}
-
-			if (Z_IS_RECURSIVE_P(struc)) {
+			zend_object *zobj = Z_OBJ_P(struc);
+			uint32_t *guard = zend_get_recursion_guard(zobj);
+			if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) {
 				PUTS("*RECURSION*\n");
 				return;
 			}
-			Z_PROTECT_RECURSION_P(struc);
+			ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj);
 
 			myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
 			class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
@@ -190,7 +191,7 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */
 				php_printf("%*c", level-1, ' ');
 			}
 			PUTS("}\n");
-			Z_UNPROTECT_RECURSION_P(struc);
+			ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj);
 			break;
 		}
 		case IS_RESOURCE: {
@@ -342,16 +343,18 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */
 		}
 		PUTS("}\n");
 		break;
-	case IS_OBJECT:
+	case IS_OBJECT: {
 		/* Check if this is already recursing on the object before calling zend_get_properties_for,
 		 * to allow infinite recursion detection to work even if classes return temporary arrays,
 		 * and to avoid the need to update the properties table in place to reflect the state
 		 * if the result won't be used. (https://github.com/php/php-src/issues/8044) */
-		if (Z_IS_RECURSIVE_P(struc)) {
+		zend_object *zobj = Z_OBJ_P(struc);
+		uint32_t *guard = zend_get_recursion_guard(zobj);
+		if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) {
 			PUTS("*RECURSION*\n");
 			return;
 		}
-		Z_PROTECT_RECURSION_P(struc);
+		ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, DEBUG, zobj);
 
 		myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_DEBUG);
 		class_name = Z_OBJ_HANDLER_P(struc, get_class_name)(Z_OBJ_P(struc));
@@ -378,8 +381,9 @@ PHPAPI void php_debug_zval_dump(zval *struc, int level) /* {{{ */
 			php_printf("%*c", level - 1, ' ');
 		}
 		PUTS("}\n");
-		Z_UNPROTECT_RECURSION_P(struc);
+		ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, DEBUG, zobj);
 		break;
+	}
 	case IS_RESOURCE: {
 		const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(struc));
 		php_printf("resource(" ZEND_LONG_FMT ") of type (%s) refcount(%u)\n", Z_RES_P(struc)->handle, type_name ? type_name : "Unknown", Z_REFCOUNT_P(struc));
@@ -553,17 +557,19 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */
 
 			break;
 
-		case IS_OBJECT:
+		case IS_OBJECT: {
 			/* Check if this is already recursing on the object before calling zend_get_properties_for,
 			 * to allow infinite recursion detection to work even if classes return temporary arrays,
 			 * and to avoid the need to update the properties table in place to reflect the state
 			 * if the result won't be used. (https://github.com/php/php-src/issues/8044) */
-			if (Z_IS_RECURSIVE_P(struc)) {
+			zend_object *zobj = Z_OBJ_P(struc);
+			uint32_t *guard = zend_get_recursion_guard(zobj);
+			if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, EXPORT, zobj)) {
 				smart_str_appendl(buf, "NULL", 4);
 				zend_error(E_WARNING, "var_export does not handle circular references");
 				return;
 			}
-			Z_PROTECT_RECURSION_P(struc);
+			ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj);
 			myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT);
 			if (level > 1) {
 				smart_str_appendc(buf, '\n');
@@ -597,7 +603,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */
 				}
 				zend_release_properties(myht);
 			}
-			Z_UNPROTECT_RECURSION_P(struc);
+			ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj);
 			if (level > 1 && !is_enum) {
 				buffer_append_spaces(buf, level - 1);
 			}
@@ -608,6 +614,7 @@ PHPAPI void php_var_export_ex(zval *struc, int level, smart_str *buf) /* {{{ */
 			}
 
 			break;
+		}
 		case IS_REFERENCE:
 			struc = Z_REFVAL_P(struc);
 			goto again;