2015-05-22 35 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

约翰:无异常http://3v4l.org/HnFQm

上一页的代码,但示出了迭代器的工作以及用()在类Foo方法getIterator的变化(由索引和取消加入) Blackbourn指出它可以在HHVM中工作:http://3v4l.org/e9qF9/通过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声明其构造函数是'final'](http://php.net/manual/en/simplexmlelement.construct。PHP),因此子类必须通过正确的工厂过程。不过'ArrayIterator'并非如此,所以也许这只是一个错误。 – IMSoP

回答

2

@Leggendario是正确的说问题在于spl_array_object_new_ex方法。但是,如果这是一个我不确定的错误。然而,这并不是真正的标准。

构造函数中的iteratorClass变量(或通过setIteratorClass设置)会暗示,只要我们从ArrayObject中检索迭代器,就会实例化此类。但它不会定期“实例化”,因为这是不可能的。

它只会初始化迭代器(分配内存等),但不会调用构造函数。因为ArrayIterator的构造函数有两个参数($array$flags),并且您的类可能已经更改了其签名,甚至可能会添加更多(必需值),所以不要调用构造函数是有意义的。

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

因为PHP会检查给定的类是否为ArrayIterator,或者其中一个父类是否为1。如果是这样,这意味着它最终可以使用这些内部值,从而直接设置它们,而不需要任何构造函数。作为缺点,任何你想构建自己的东西都不会被调用。

+0

实际上,我可以覆盖'ArrayIterator'子类中的方法,并按预期调用它们:https://github.com/xwp/wp-customize-widgets-plus/blob/8a9cd09eae1e936378add62ddb992460649d9573/php/class-widget-settings。 php#L51-L141 –

+0

你可以看到'offsetGet()'可以在所有具有'ArrayIterator'的PHP版本中被成功覆盖(5.0.0-5.0.3除外):http://3v4l.org/tJgV5 –

+0

你是对的。我误解了代码,并用一个错误的测试来检查它(将这些方法添加到arrayobject而不是arrayiterator)。相反,它所做的是将给定类('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/R8PHr

+0

您能否调整我的示例中的代码以使其回显PASS?我想看看如何获​​得实际调用的'FooIterator :: __ construct()'的演示。这是在我的例子中抛出异常的唯一目的。 –

+0

@WestonRuter检查了这一点:[链接](http://3v4l.org/HnFQm)只需重写函数getIterator()。 –

+0

@WestonRuter也检查这一点:没有例外,但迭代和unset'ing:http://3v4l.org/R8PHr –

相关问题