Не можете написать новые элементы в IteratorAggregate? - PullRequest
2 голосов
/ 27 августа 2010

Правильно ли я считаю, что IteratorAggregate предоставляет только массив read доступ к объекту?Если мне нужно записать объект-массив, то мне нужно использовать 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 ]

4 голосов
/ 28 августа 2010

Что бы это ни стоило, учитывая ваш примерный класс, вы могли бы просто использовать 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
4 голосов
/ 27 августа 2010

Хорошо, теперь, когда я лучше понимаю, что вы имеете в виду, позвольте мне попытаться объяснить здесь.

Реализация итератора (через интерфейс 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 class .Каждый интерфейс в этом списке предоставляет возможность использовать определенную функцию ядра.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), который зависит от состояния исходных объектов.Но обычно это не так
...