Не можете написать новые элементы в IteratorAggregate?
Правильно ли я считаю, что 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
2 ответа
Что бы это ни стоило, учитывая ваш примерный класс, вы могли бы просто использовать 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
Хорошо, теперь, когда я лучше понимаю, что вы имеете в виду, позвольте мне попытаться объяснить здесь.
Реализация итератора (либо через Iterator
или же IteratorAggregate
интерфейс) только добавит функциональность при итерации. Что это значит, это влияет только на способность использовать 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);
, Таким образом, вы можете продолжить итерацию по всему объекту, как это было в начале итерации.
Обратите внимание на это ключевое отличие. Каждая итерация Iterator
будет зависеть от состояния объекта в данный момент времени. Каждая итерация IteratorAggregate
будет зависеть от состояния объекта в начале итерации. * Имеет ли это смысл?
Теперь, что касается вашей конкретной ошибки. Это никак не связано с итераторами. Вы пытаетесь получить доступ (написать на самом деле) к переменной вне итерации. Так что он вообще не включает итератор (за исключением того, что вы пытаетесь проверить результаты с помощью итерации).
Вы пытаетесь обработать объект как массив ($objFoo['reeb'] = 'roob';
). Теперь для нормальных объектов это невозможно. Если вы хотите сделать это, вам нужно реализовать ArrayAccess
интерфейс Обратите внимание, что вам не нужно определять итератор, чтобы использовать этот интерфейс. Все, что он делает - это предоставляет возможность доступа к определенным элементам объекта с использованием массива, подобного синтаксису.
Обратите внимание, я сказал, что массив, как. Вы не можете рассматривать это как массив в целом. sort
функции никогда не будут работать. Однако есть несколько других интерфейсов, которые вы можете реализовать, чтобы сделать объект более похожим на массив. Одним из них является Countable
интерфейс, который дает возможность использовать count()
функция на объекте (count($obj)
).
Для примера в ядре объединения всех этих в один класс, проверьте ArrayObject
класс Каждый интерфейс в этом списке предоставляет возможность использовать определенную функцию ядра. IteratorAggregate
предоставляет возможность использовать foreach
, ArrayAccess
предоставляет возможность доступа к объекту с использованием синтаксиса, похожего на массив. Serializable
Интерфейс обеспечивает возможность serialize
а также deserialize
данные в конкретной усадьбе. Countable
Интерфейс позволяет считать объект как массив.
Таким образом, эти интерфейсы никак не влияют на основную функциональность объекта. Вы по-прежнему можете делать с ним все, что можете сделать с обычным объектом (например, $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
свойство напрямую (как и в обычном ООП). Просто замените линию $objFoo['reeb'] = 'roob';
с $objFoo->_array['reeb'] = 'roob';
, Это сделает то же самое....
- Обратите внимание, что это поведение по умолчанию. Вы можете взломать внутренний итератор (итератор возвращается
IteratorAggreagate::getIterator
) это зависит от состояния исходных объектов. Но это не так, как это обычно делается