为什么从不调用 ArrayIterator 子类的构造函数?

Why is ArrayIterator subclass's constructor never called?

我很困惑为什么 ArrayIterator 的子类从未调用其 __construct 方法。考虑这个例子:

<?php

class ConstructorException extends Exception {}

class Foo extends ArrayObject {
    function __construct( $arr = array(), $flags = 0, $iterator = 'ArrayIterator' ) {
        $iterator = 'FooIterator';
        parent::__construct( $arr, $flags, $iterator );
    }
}

class FooIterator extends ArrayIterator {
    function __construct( $array = array(), $flags = 0 ) {
        throw new ConstructorException( 'HELLO WORLD' ); // I AM NEVER CALLED.
        parent::__construct( $array, $flags );
    }
}

try {
    $f = new Foo( array( 1, 2, 3 ) );
    $it = $f->getIterator();
    if ( get_class( $it ) !== 'FooIterator' ) {
        throw new Exception( 'Expected iterator to be FooIterator.' );
    }
    die( "FAIL\n" );
} catch ( ConstructorException $e ) {
    die( "PASS\n" );
} catch ( \Exception $e ) {
    die( sprintf( "ERROR: %s\n", $e->getMessage() ) );
}

在 PHP 5.4 和 5.5 中,结果都是 FAIL。为什么?

@Leggendario 说得对,问题出在 spl_array_object_new_ex 方法上。但是,如果这是一个错误,我不确定。然而,这里发生的事情并不是真正的标准。

构造函数中的 iteratorClass 变量(或通过 setIteratorClass 设置)表明每当我们从 ArrayObject 中检索迭代器时都会实例化此 class。但它不执行常规 "instantiation",因为这是不可能的。

它只会初始化迭代器(分配内存等),但不会调用构造函数。不调用构造函数是有意义的,因为 ArrayIterator 的构造函数有两个参数($array$flags),而你的 class 可能已经改变了它的签名,也许甚至添加更多(强制值)。

通常 ArrayIterator(或 RecursiveArrayIterator)是内部 classes 并且有一个内部结构附加​​到它们(基本上就像它自己的内部属性集,你无法获得直接从 PHP userland)。 spl_array_object_new_ex 将直接设置这些内部值(最值得注意的是 ce_get_iteratorar_flags)。所以基本上它接管了 ArrayIterator 构造函数的工作。

因为 PHP 检查给定的 class 是否是一个 ArrayIterator,或者父 class 是否是一个。如果是这样,这意味着它最终可以使用这些内部值并因此直接设置它们,绕过对构造函数的任何需要。不利的是,您想要自己构建的任何内容都不会被调用。

方法 __construct 像往常一样在创建新实例时被调用:

$it = new FooIterator();

所以,这需要一些时间,我有一个解决方案:覆盖方法 getIterator() 在你的 class Foo (ArrayObject 的 subclass)在你的下一个例子中:

class Foo extends ArrayObject {

    public function __construct( $arr = array(), $flags = 0, $iterator = 'FooIterator' ) {
        parent::__construct( $arr, $flags, $iterator );
    }

    /**
     * @return ArrayIterator
     */
    public function getIterator()
    {
        $class = $this->getIteratorClass();
        return new $class($this);
    }
}

通过此更正,您的代码将 'PASS'。

问题代码更改结果:http://3v4l.org/HnFQm

前面的代码无一例外,但显示迭代器在 class Foo 中的方法 getIterator() 的更改下运行良好(按索引添加和取消设置):http://3v4l.org/R8PHr