2

我认为IteratorAggregate只提供对对象的类似数组的读取访问权限是否正确?如果我需要将对象作为数组写入,那么我需要使用Iterator?

尝试添加新元素时导致致命错误的 IteratorAggregate 对象的演示如下:

<?

class foo implements IteratorAggregate {

    public $_array = array('foo'=>'bar', 'baz'=>'quux');

    public function getIterator() {
        return new ArrayIterator($this->_array);
    }

} // end class declaration


$objFoo = & new foo();

foreach ( $objFoo as $key => $value ) {
        echo "key: $key, value: $value\n";
}

$objFoo['reeb'] = "roob";

foreach ( $objFoo as $key => $value ) {
        echo "key: $key, value: $value\n";
}

$ php test.php
key: foo, value: bar
key: baz, value: quux

Fatal error: Cannot use object of type foo as array in /var/www/test.php on line 20

Call Stack:
    0.0009      57956   1. {main}() /var/www/test.php:0
4

2 回答 2

4

对于它的价值,给定您的示例类,您可以使用ArrayObject(实现IteratorAggregate并且ArrayAccess像我们想要的那样)。

class Foo extends ArrayObject
{
    public function __construct()
    {
        parent::__construct(array('foo' => 'bar', 'baz' => 'quux'));
    }
}

$objFoo = new Foo();

foreach ( $objFoo as $key => $value ) {
    echo "$key => $value\n";
}

echo PHP_EOL;

$objFoo['foo'] = 'badger';
$objFoo['baz'] = 'badger';
$objFoo['bar'] = 'badger';

foreach ( $objFoo as $key => $value ) {
    echo "$key => $value\n";
}

正如预期的那样,输出是:

foo => bar
baz => quux

foo => badger
baz => badger
bar => badger
于 2010-08-27T20:50:22.290 回答
4

好的,现在我更好地理解了您的意思,让我在这里尝试解释一下。

实现迭代器(通过IteratorIteratorAggregate接口)只会在迭代时添加功能。这意味着,它只影响使用foreach. 它不会改变或改变对象的任何其他用途。两者都不直接支持向迭代器“写入”。我直接说,因为您仍然可以在迭代时写入基础对象,但不能在 foreach 内部写入。

这意味着:

foreach ($it as $value) {
    $it->foo = $value;
}

可以正常工作(因为它正在写入原始对象)。

尽管

foreach ($it as &$value) {
    $value = 'bar';
}

很可能不会工作,因为它试图通过迭代器进行写入(不直接支持。它可能可以完成,但它不是核心设计)。

要了解原因,让我们看看幕后发生了什么foreach ($obj as $value)。它基本上等同于:

对于Iterator课程:

$obj->rewind();
while ($obj->valid()) {
    $value = $obj->current();
    // Inside of the loop here
    $obj->next();
}

对于IteratorAggregate课程:

$it = $obj->getIterator();
$it->rewind();
while ($it->valid()) {
    $value = $it->current();
    // Inside of the loop here
    $it->next();
}

现在,你明白为什么不支持写作了吗? $iterator->current()不返回参考。所以你不能直接使用foreach ($it as &$value).

现在,如果您想这样做,您需要更改迭代器以从current(). 但是,这会破坏接口(因为它会改变方法签名)。所以这是不可能的。这意味着无法写入迭代器。

但是,要意识到它只影响迭代本身。它与从任何其他上下文或任何其他庄园访问对象无关。 $obj->bar迭代器对象与非迭代器对象完全相同。

Iterator现在,和之间有了区别IteratorAggregate。回头看看while等价物,你可能会看到不同之处。假设我们这样做:

foreach ($objFoo as $value) {
    $objFoo->_array = array();
    print $value . ' - ';
}

会发生什么?使用Iterator,它只会打印第一个值。这是因为迭代器在每次迭代时直接对对象进行操作。而且由于您更改了对象迭代的内容(内部数组),因此迭代发生了变化。

现在,如果$objFoo是一个IteratorAggregate,它将打印迭代开始时存在的所有值。那是因为您在返回new ArrayIterator($this->_array);. 因此,您可以继续迭代整个对象,就像它在迭代开始时一样。

请注意关键的区别。a 的每次迭代Iterator都将取决于对象在该时间点的状态。a 的每次迭代IteratorAggregate都将取决于迭代开始时对象的状态。* 这有意义吗?

现在,至于您的具体错误。它根本与迭代器无关。您正在尝试访问(实际写入)迭代之外的变量。所以它根本不涉及迭代器(除了你试图通过迭代来检查结果)。

您正在尝试将对象视为数组 ( $objFoo['reeb'] = 'roob';)。现在,对于普通物体,这是不可能的。如果你想这样做,你需要实现ArrayAccess接口。请注意,您不必为了使用该接口而定义迭代器。它所做的只是提供使用类似数组的语法访问对象的特定元素的能力。

注意我说的是数组。通常,您不能将其视为数组。 sort功能永远不会起作用。但是,您可以实现一些其他接口来使对象更像数组。一种是能够在对象上使用函数的Countable接口( )。count()count($obj)

有关将所有这些组合成一个类的核心示例,请查看ArrayObjectclass。该列表中的每个接口都提供了使用内核特定功能的能力。提供IteratorAggregate了使用foreach. ArrayAccess提供了使用类似数组的语法访问对象的能力。该接口提供了特定庄园Serializable的能力serialize和数据。deserializeCountable接口允许像数组一样对对象进行计数。

所以这些接口不会以任何方式影响对象的核心功能。你仍然可以用它做任何你可以对普通对象做的事情(例如$obj->property$obj->method())。然而,他们所做的是提供在核心的某些位置使用对象(如数组)的能力。每个都在严格的范围内提供功能(这意味着您可以使用它们的任何组合。您不需要能够像数组一样访问对象才能访问count()它)。这才是这里真正的力量。

哦,不推荐使用按引用分配的返回值new,所以没有必要这样做......

因此,至于您的具体问题,这是一种解决方法。我只是在ArrayAccess你的类中实现了接口,这样$objFoo['reeb'] = 'roob';就可以了。

class foo implements ArrayAccess, IteratorAggregate {

    public $_array = array('foo'=>'bar', 'baz'=>'quux');

    public function getIterator() {
        return new ArrayIterator($this->_array);
    }

    public function offsetExists($offset) {
        return isset($this->_array[$offset]);
    }

    public function offsetGet($offset) {
        return isset($this->_array[$offset]) ? $this->_array[$offset] : null;
    }

    public function offsetSet($offset, $value) {
        $this->_array[$offset] = $value;
    }

    public function offsetUnset($offset) {
        if (isset($this->_array[$offset]) {
            unset($this->_array[$offset]);
        }
    }
}

然后,您可以尝试现有代码:

$objFoo = & new foo();

foreach ( $objFoo as $key => $value ) {
    echo "key: $key, value: $value\n";
}

$objFoo['reeb'] = "roob";

foreach ( $objFoo as $key => $value ) {
    echo "key: $key, value: $value\n";
}

它应该可以正常工作:

key: foo, value: bar
key: baz, value: quux
key: foo, value: bar
key: baz, value: quux
key: reeb, value: roob

您也可以通过$objFoo->_array直接更改属性来“修复”它(就像常规 OOP 一样)。只需将行替换$objFoo['reeb'] = 'roob';$objFoo->_array['reeb'] = 'roob';. 这将完成同样的事情......

  • 请注意,这是默认行为。您可以将一个内部迭代器(由 返回的迭代器IteratorAggreagate::getIterator)组合在一起,该迭代器确实取决于原始对象的状态。但这不是通常的做法
于 2010-08-27T14:41:47.753 回答