Подтвердить что ты не робот

Почему конструктор подкласса ArrayIterator никогда не вызывал?

Я сбиваю с толку, почему подкласс до 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. Почему?

4b9b3361

Ответ 1

@Leggendario имеет право сказать, что проблема заключается в методе spl_array_object_new_ex. Однако, если это ошибка, я не уверен. Это, однако, не совсем стандартно, что здесь происходит.

Переменная iteratorClass от конструктора (или через setIteratorClass) предполагает, что этот класс создается при каждом изъятии итератора из ArrayObject. Но он не выполняет регулярные "экземпляры", поскольку это невозможно.

Он просто инициализирует итератор (выделяет память и т.д.), но не вызывает конструктор. Имеет смысл не вызывать конструктор, поскольку конструктор ArrayIterator принимает два аргумента ($array и $flags), и ваш класс мог бы изменить свою подпись, возможно, даже добавить больше (обязательные значения).

Обычно ArrayIterator (или RecursiveArrayIterator) являются внутренними классами и к ним привязана внутренняя структура (в основном, как и собственный внутренний набор свойств, которые вы не можете получить непосредственно из пользовательского пространства PHP). spl_array_object_new_ex будет устанавливать эти внутренние значения напрямую (в первую очередь, ce_get_iterator и ar_flags). Таким образом, в основном он берет на себя работу конструктора ArrayIterator.

Поскольку PHP проверяет, является ли данный класс ArrayIterator, или если один из родительских классов - один. Когда это так, это означает, что в конечном итоге он может использовать эти внутренние значения и, таким образом, устанавливает их напрямую, минуя необходимость в конструкторе. Как недостаток, все, что вы хотели бы построить самостоятельно, не будет называться.

Ответ 2

Метод __ Конструкция вызывалась, как обычно, при создании нового экземпляра:

$it = new FooIterator();

Итак, это займет некоторое время, и у меня есть решение: переопределить метод getIterator() в классе Foo (подкласс ArrayObject) в следующем примере:

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

Предыдущий код без исключения, но показывающий, что итератор хорошо работает с изменениями метода getIterator() в классе Foo (добавление по индексу и отмена): http://3v4l.org/R8PHr