Es können keine neuen Elemente zu IteratorAggregate schreiben?
Frage
Am mich richtig, dass IteratorAggregate
nur im Denken bietet Array-artige lesen Zugriff auf ein Objekt? Wenn ich auf das Objekt-as-Array schreiben müssen, dann muss ich verwenden Iterator
?
Demonstration eines IteratorAggregate Objekt einen fatalen Fehler zu verursachen, wenn ein neues Element hinzuzufügen versuchen folgt:
<?
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
Lösung
Für das, was es wert ist, Ihre Beispielklasse gegeben, die Sie gerade gemacht Nutzung des ArrayObject
konnte (die Geräte IteratorAggregate
und ArrayAccess
wie wir wollen).
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";
}
Mit dem Ausgang Wesen, wie zu erwarten:
foo => bar
baz => quux
foo => badger
baz => badger
bar => badger
Andere Tipps
Ok, jetzt, dass ich noch besser verstehen, was Sie meinen, lassen Sie mich versuchen, hier zu erklären.
einen Iterator Implementierung (entweder über die Iterator
oder IteratorAggregate
Schnittstelle) nur Funktionen hinzufügen, wenn das Iterieren. Was das bedeutet, ist es wirkt sich nur auf die Fähigkeit zur Nutzung foreach
. Dabei spielt es keine andere Nutzung des Objekts ändern oder verändern. Keiner von denen Unterstützung direkt „Schreiben“ an den Iterator. Ich sage direkt, da man immer noch auf das Basisobjekt schreiben kann, während das Iterieren, aber nicht innerhalb der foreach.
Das bedeutet, dass:
foreach ($it as $value) {
$it->foo = $value;
}
wird gut funktionieren (da es schriftlich auf das ursprüngliche Objekt).
Während
foreach ($it as &$value) {
$value = 'bar';
}
Die meisten werden wahrscheinlich nicht funktionieren, da es zu schreiben durch den Iterator versucht (was nicht direkt unterstützt wird. Es kann sein kann getan werden, aber es ist nicht das Kern-Design).
Um zu sehen, warum schauen wir uns an, was mit diesem foreach ($obj as $value)
hinter den Kulissen passiert. Es ist im Grunde identisch mit:
Für Iterator
Klassen:
$obj->rewind();
while ($obj->valid()) {
$value = $obj->current();
// Inside of the loop here
$obj->next();
}
Für IteratorAggregate
Klassen:
$it = $obj->getIterator();
$it->rewind();
while ($it->valid()) {
$value = $it->current();
// Inside of the loop here
$it->next();
}
Nun, sehen Sie, warum Schreiben nicht unterstützt wird? $iterator->current()
kehrt nicht einen Verweis. Sie können also nicht direkt mit foreach ($it as &$value)
schreiben.
Nun, wenn man will, das tun, müssen Sie den Iterator zu verändern, einen Verweis von current()
zurückzukehren. Dies würde jedoch bricht die Schnittstelle (da sie die Methoden Signatur ändern würden). Das ist also nicht möglich. Das heißt also, dass das Schreiben an die Iterator ist nicht möglich.
Allerdings realize, dass es wirkt sich nur auf die Iteration selbst. Es hat nichts mit dem Zugriff auf das Objekt aus einem anderen Zusammenhang oder in irgendeine anderen Herren zu tun. $obj->bar
wird genau das gleiche für ein Iterator-Objekt, wie es für eine nicht Iteratorobjekt ist.
Nun, es kommt ein Unterschied zwischen den Iterator
und IteratorAggregate
. Schauen Sie sich die while
Äquivalenzen zurück, und Sie können in der Lage, den Unterschied zu sehen. Angenommen, wir dies taten:
foreach ($objFoo as $value) {
$objFoo->_array = array();
print $value . ' - ';
}
Was würde passieren? Mit einem Iterator
wäre es nur der erste Wert drucken. Das ist, weil der Iterator arbeitet direkt auf dem Objekt für jede Iteration. Und da Sie geändert, was das Objekt iteriert auf (das interne Array), die Iteration ändert.
Nun, wenn $objFoo
ein IteratorAggregate
ist, würde es alle Werte drucken, die zu Beginn der Iteration existierte. Das ist, weil Sie eine Kopie des Arrays aus, wenn Sie die new ArrayIterator($this->_array);
zurückgegeben. So Sie iterieren das gesamte Objekt fortsetzen können, wie es zu Beginn der Iteration war.
Beachten Sie, dass wesentlicher Unterschied. Jede Iteration eines Iterator
hängt von dem Zustand des Objekts zu diesem Zeitpunkt abhängen. Jede Iteration eines IteratorAggregate
hängt von dem Zustand des Objekts zu Beginn der Iteration abhängig sein. * Ist das sinnvoll?
Jetzt, da für Ihre spezifischen Fehler. Es hat nichts mit Iteratoren überhaupt zu tun. Sie versuchen, den Zugriff (Schreiben tatsächlich) eine Variable außerhalb der Iteration. So ist es nicht den Iterator überhaupt nicht beteiligt (mit der Ausnahme, dass Sie die Ergebnisse zu überprüfen, indem das Iterieren sind versuchen).
Sie versuchen, das Objekt als Array ($objFoo['reeb'] = 'roob';
) zu behandeln. Nun, für normale Objekte, das ist nicht möglich. Wenn Sie das tun wollen, müssen Sie die ArrayAccess
Schnittstelle implementieren . Beachten Sie, dass Sie haben müssen, um einen Iterator nicht definiert, um diese Schnittstelle zu verwenden. Denn es macht nicht ist die Möglichkeit, den Zugriff auf bestimmte Elemente eines Objekts bieten eine Reihe wie Syntax.
Beachten Sie, sagte ich Array wie. Sie können es nicht wie ein Array im Allgemeinen behandeln. sort
Funktionen wird nie funktionieren. Allerdings gibt es ein paar andere Schnittstellen,Sie können implementieren wie ein Objekt mehr Array zu machen. Eine davon ist die Countable
Schnittstelle , die die Fähigkeit ermöglicht die count()
Funktion zu verwenden auf ein Objekt (count($obj)
).
Für ein Beispiel im Kern all dieser in eine Klasse Kombination Besuche die ArrayObject
Klasse . Jede Schnittstelle in dieser Liste bietet die Möglichkeit, eine bestimmte Funktion des Kerns zu verwenden. Die IteratorAggregate
bietet die Möglichkeit, die Verwendung foreach
. Die ArrayAccess
bietet die Möglichkeit, das Objekt mit einer Array-ähnlicher Syntax zuzugreifen. Die Serializable
Schnittstelle bietet die Möglichkeit, serialize
und deserialize
die Daten in einem bestimmten Manor. Die Countable
Schnittstelle ermöglicht zum Zählen des Objekts wie ein Array.
So diese Schnittstellen haben keinen Einfluss auf die Kernfunktionalität des Objekts in irgendeiner Weise. Sie können immer noch mit ihm tun alles, was Sie zu einem normalen Objekt tun könnten (wie $obj->property
oder $obj->method()
). Was sie tun, ist jedoch die Möglichkeit bieten, das Objekt wie ein Array an bestimmten Stellen im Kern zu verwenden. Jeder bietet die Funktionalität in einem strengen Rahmen (Was bedeutet, dass Sie eine beliebige Kombination von ihnen verwenden können, dass Sie möchten. Sie haben nicht auf das Objekt zuzugreifen wie ein Array in der Lage sein müssen in der Lage sein, es zu count()
). Das ist die wahre Macht hier.
Oh, und Zuweisung der Rückgabewert von new
durch Referenz deprecated , so gibt es keine Notwendigkeit, dass ...
So, wie für Ihr spezielles Problem, hier ist ein Weg, um ihn herum. Ich habe einfach die ArrayAccess
Schnittstelle in Ihre Klasse implementiert, so dass $objFoo['reeb'] = 'roob';
arbeiten.
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]);
}
}
}
Dann können Sie Ihre vorhandenen Code versuchen:
$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";
}
Und es sollte funktionieren:
key: foo, value: bar
key: baz, value: quux
key: foo, value: bar
key: baz, value: quux
key: reeb, value: roob
Sie könnten auch als „fix“ durch die $objFoo->_array
Eigenschaft direkt zu ändern (wie normale OOP). Einfach die Linie $objFoo['reeb'] = 'roob';
mit $objFoo->_array['reeb'] = 'roob';
ersetzen. Das wird das Gleiche erreichen ....
- Beachten Sie, dass dies das Standardverhalten ist. Sie könnten eine innere Iterator (der Iterator zurück von
IteratorAggreagate::getIterator
) hacken zusammen, die auf dem ursprünglichen Objekte Zustand abhängt. Aber das ist nicht, wie es normalerweise getan