From 572652e5def9a541d55f2e66dd48eb0e0d5a24b1 Mon Sep 17 00:00:00 2001 From: Alexandre Daubois <2144837+alexandre-daubois@users.noreply.github.com> Date: Wed, 22 Oct 2025 10:01:35 +0200 Subject: [PATCH] Fix GH-20217: ReflectionClass::isIterable() should return false for classes with property hooks (#20241) --- NEWS | 4 ++ ext/reflection/php_reflection.c | 3 +- .../ReflectionClass_isIterable_gh20217.phpt | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 ext/reflection/tests/ReflectionClass_isIterable_gh20217.phpt diff --git a/NEWS b/NEWS index 7d24c95d5b9..ebf82ed1be6 100644 --- a/NEWS +++ b/NEWS @@ -67,6 +67,10 @@ PHP NEWS - Random: . Fix Randomizer::__serialize() w.r.t. INDIRECTs. (nielsdos) +- Reflection: + . Fixed bug GH-20217 (ReflectionClass::isIterable() incorrectly returns true + for classes with property hooks). (alexandre-daubois) + - SimpleXML: . Partially fixed bug GH-16317 (SimpleXML does not allow __debugInfo() overrides to work). (nielsdos) diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index f79d8a5181d..e718b1815fb 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5696,7 +5696,8 @@ ZEND_METHOD(ReflectionClass, isIterable) RETURN_FALSE; } - RETURN_BOOL(ce->get_iterator || instanceof_function(ce, zend_ce_traversable)); + RETURN_BOOL((ce->get_iterator && ce->get_iterator != zend_hooked_object_get_iterator) + || instanceof_function(ce, zend_ce_traversable)); } /* }}} */ diff --git a/ext/reflection/tests/ReflectionClass_isIterable_gh20217.phpt b/ext/reflection/tests/ReflectionClass_isIterable_gh20217.phpt new file mode 100644 index 00000000000..f64fc1efcd4 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_isIterable_gh20217.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-20217 (ReflectionClass::isIterable() should return false for classes with property hooks) +--FILE-- + 'virtual'; + } +} + +class IterableClassWithPropertyHooks implements IteratorAggregate +{ + public string $name { + get => 'virtual'; + } + + public function getIterator(): Traversable + { + return new ArrayIterator([]); + } +} + +$classes = [ + 'ClassWithPropertyHooks' => false, + 'IterableClassWithPropertyHooks' => true, +]; + +foreach ($classes as $className => $expected) { + $status = (new ReflectionClass($className)->isIterable() === $expected) ? 'PASS' : 'FAIL'; + echo "$className: $status\n"; +} + +?> +--EXPECT-- +ClassWithPropertyHooks: PASS +IterableClassWithPropertyHooks: PASS