2015-05-22 2 views
13

Я сбиваю с толку, почему подкласс до ArrayIterator никогда не получает вызванный метод __construct. Рассмотрим следующий пример:Почему конструктор подкласса ArrayIterator никогда не вызывал?

<?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. Зачем?

+1

Джон Blackbourn отмечает, что он работает в HHVM: http://3v4l.org/e9qF9/via https://twitter.com/johnbillion/status/601900452293300224 –

+2

Кажется, внутренняя проблема в php. [spl_array_object_new_ex] (https://github.com/php/php-src/blob/master/ext/spl/spl_array.c#L143) не вызывают конструктор родителя. Следует сообщить об этом? – Federkun

+0

Конструкторы встроенных объектов немного фанки - обратите внимание, например, что [SimpleXMLElement объявляет свой конструктор «последним»] (http://php.net/manual/en/simplexmlelement.construct.php), так что подклассы должны пройти правильную заводскую процедуру. Это не относится к 'ArrayIterator', хотя, возможно, это просто ошибка. – IMSoP

ответ

2

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

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

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

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

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

+0

На самом деле, я могу переопределить методы в подклассе 'ArrayIterator', и они вызываются как ожидалось: https://github.com/xwp/wp-customize-widgets-plus/blob/8a9cd09eae1e936378add62ddb992460649d9573/php/class-widget-settings. php # L51-L141 –

+0

Вы можете видеть, что 'offsetGet()' может быть успешно переопределено во всех версиях PHP, у которых есть 'ArrayIterator' (кроме 5.0.0 - 5.0.3): http://3v4l.org/tJgV5 –

+0

Да ты прав. Я неправильно прочитал код и проверил его с неправильным тестом (добавив методы в массивный объект, а не в матрица). Вместо этого он выполняет ручное копирование/кэширование некоторых методов из данного класса ('FooIterator') во внутренние функции итератора. Скорее всего, либо ускорить работу, либо из-за отсутствия «правильной» инициализации класса в первую очередь. – JayTaph

3

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

$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

+0

Можете ли вы адаптировать код в моем примере, чтобы заставить его эхо PASS? Я хочу увидеть демонстрацию того, как вызвать 'FooIterator :: __ construct()' фактически вызванный. Это единственная цель - исключить исключение в моем примере. –

+0

@WestonRuter проверить это: [link] (http://3v4l.org/HnFQm) Просто переопределить функцию getIterator(). –

+0

@WestonRuter также проверяет это: без исключения, но с итерацией и отключением: http://3v4l.org/R8PHr –

Смежные вопросы