Skip to content

Class with property hooks causes ReflectionClass->isIterable() to return true on PHP8.4 #20217

@lisachenko

Description

@lisachenko

Description

The following code:

<?php
class ClassWithPhp84PropertyHooks
{
    private string $backing = 'default';

    public string $name {
        get => $this->backing;
        set => $this->backing = strtoupper($value);
    }
}

$refClass = new ReflectionClass(ClassWithPhp84PropertyHooks::class);
var_dump($refClass->isIterable());

Resulted in this output:

true

But I expected this output instead:

false

Copilot analysis:

ReflectionClass::isIterable() returns true because the engine considers the class iterable when its internal class entry has a non‑NULL get_iterator handler — and classes that use the new property hooks end up with such a handler set. isIterable() checks ce->get_iterator || instanceof Traversable, so the presence of the get_iterator pointer makes it return true even if the class does not implement Traversable.
What the code does (evidence)

The ReflectionClass::isIterable implementation (ext/reflection/php_reflection.c) returns false for interfaces/traits/abstracts, otherwise it returns: RETURN_BOOL(ce->get_iterator || instanceof_function(ce, zend_ce_traversable)); i.e. it is true when either the class has a get_iterator handler or it implements Traversable.
Why property hooks flip that bit

The property-hook implementation in the engine installs/customizes object handlers so that property access and enumeration can be handled by the new hook code. As part of that support the class entry ends up with a get_iterator handler (so the engine can enumerate properties through the new hooks). Because isIterable() only looks at the get_iterator pointer (or Traversable), Reflection reports the class as iterable.
Practical consequence

ReflectionClass::isIterable() returning true in this case is expected engine behaviour. It does not necessarily mean the class implements Traversable/IteratorAggregate; it means the Zend class entry provides an iterator function for foreach/iteration. You can confirm with reflection checks, e.g.: $r = new ReflectionClass(ClassWithPhp84PropertyHooks::class); var_dump($r->isIterable(), $r->implementsInterface(Traversable::class)); // likely: true, false
References

PHP Version

PHP 8.4.13 (cli) (built: Sep 23 2025 14:14:32) (NTS)
Copyright (c) The PHP Group
Built by Homebrew
Zend Engine v4.4.13, Copyright (c) Zend Technologies
    with Xdebug v3.4.1, Copyright (c) 2002-2025, by Derick Rethans
    with Zend OPcache v8.4.13, Copyright (c), by Zend Technologies

Operating System

MacOS

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions