2010-08-27 25 views
2

Ai-je raison de penser que IteratorAggregate ne fournit que des tableaux lire accéder à un objet? Si je dois écrire sur l'objet en tant que tableau, alors je dois utiliser Iterator?Impossible d'écrire de nouveaux éléments dans IteratorAggregate?

Démonstration d'un objet IteratorAggregate provoquant une erreur fatale lors d'une tentative d'ajouter un nouvel élément suivant:

<? 

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 

Répondre

4

Pour ce que ça vaut la peine, compte tenu de votre classe exemple, vous auriez pu fait usage de la ArrayObject (qui met en œuvre IteratorAggregate et ArrayAccess comme nous voulons).

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"; 
} 

Avec l'être de sortie, comme prévu:

foo => bar 
baz => quux 

foo => badger 
baz => badger 
bar => badger 
4

Ok, maintenant que je comprends mieux ce que vous voulez dire, je vais essayer de expliquer ici.

L'implémentation d'un itérateur (via l'interface Iterator ou IteratorAggregate) ne fera qu'ajouter des fonctionnalités lors de l'itération. Qu'est-ce que cela signifie, est-ce que cela affecte uniquement la possibilité d'utiliser foreach. Il ne modifie ni ne modifie aucune autre utilisation de l'objet. Aucun d'entre eux ne supporte directement "l'écriture" de l'itérateur. Je dis directement, puisque vous pouvez toujours écrire sur l'objet de base pendant l'itération, mais pas à l'intérieur du foreach.

Cela signifie que:

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

fonctionne très bien (car il est écrit à l'objet original).

Alors que

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

Très probablement ne fonctionnera pas car il est en train d'écrire à travers le (Il peut être en mesure de faire qui ne sont pas directement pris en charge., Mais pas la conception de base) iterator. Pour voir pourquoi, regardons ce qui se passe dans les coulisses avec le foreach ($obj as $value). Il est fondamentalement identique à:

Pour Iterator cours:

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

Pour IteratorAggregate cours:

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

Maintenant, voyez-vous pourquoi l'écriture ne sont pas pris en charge? $iterator->current() ne renvoie pas de référence. Vous ne pouvez donc pas écrire directement en utilisant foreach ($it as &$value). Maintenant, si vous voulez faire cela, vous devez modifier l'itérateur pour renvoyer une référence de current(). Cependant, cela casserait l'interface (puisque cela changerait la signature des méthodes). Donc ce n'est pas possible. Cela signifie donc que l'écriture dans l'itérateur n'est pas possible. Toutefois, sachez que cela n'affecte que l'itération elle-même. Cela n'a rien à voir avec l'accès à l'objet depuis n'importe quel autre contexte ou dans n'importe quel autre manoir. $obj->bar sera exactement le même pour un objet itérateur que pour un objet non itérateur. Maintenant, il y a une différence entre Iterator et IteratorAggregate. Revenez sur les équivalences while, et vous pourrez peut-être voir la différence.Supposons que nous l'ayons fait:

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

Que se passerait-il? Avec un Iterator, il imprime seulement la première valeur. C'est parce que l'itérateur opère directement sur l'objet pour chaque itération. Et puisque vous avez changé ce sur quoi l'objet itère (le tableau interne), l'itération change.

Maintenant, si $objFoo est un IteratorAggregate, il affichera toutes les valeurs qui existaient au début de l'itération. C'est parce que vous avez fait une copie du tableau lorsque vous avez retourné le new ArrayIterator($this->_array);. Vous pouvez donc continuer à parcourir tout l'objet tel qu'il était au début de l'itération.

Notez cette différence de clé. Chaque itération d'un Iterator dépendra de l'état de l'objet à ce moment-là. Chaque itération d'un IteratorAggregate dépendra de l'état de l'objet au début de l'itération. * Cela a-t-il un sens?

Maintenant, en ce qui concerne votre erreur spécifique. Cela n'a rien à voir avec les itérateurs. Vous essayez d'accéder (écrire réellement) à une variable en dehors de l'itération. Cela n'implique donc pas du tout l'itérateur (sauf que vous essayez de vérifier les résultats en itérant).

Vous essayez de traiter l'objet sous la forme d'un tableau ($objFoo['reeb'] = 'roob';). Maintenant, pour les objets normaux, ce n'est pas possible. Si vous voulez faire cela, vous devez implémenter le ArrayAccess interface. Notez que vous n'avez pas besoin d'avoir un itérateur défini pour utiliser cette interface. Tout ce qu'il fait est de fournir la possibilité d'accéder à des éléments spécifiques d'un objet en utilisant une syntaxe de type tableau.

note J'ai dit tableau comme. Vous ne pouvez pas le traiter comme un tableau en général. Les fonctions sort ne fonctionneront jamais. Cependant, il existe quelques autres interfaces que vous pouvez implémenter pour rendre un objet plus semblable à un tableau. L'un est le Countable interface qui permet d'utiliser la fonction count() sur un objet (count($obj)).

Pour un exemple dans le noyau de combiner tous ces dans une classe, consultez le ArrayObject class. Chaque interface de cette liste offre la possibilité d'utiliser une caractéristique spécifique du cœur. Le IteratorAggregate permet d'utiliser foreach. Le ArrayAccess fournit la possibilité d'accéder à l'objet avec une syntaxe de type tableau. L'interface Serializable fournit la capacité de serialize et deserialize les données dans un manoir spécifique. L'interface Countable permet de compter l'objet comme un tableau. Par conséquent, ces interfaces n'affectent en aucune manière la fonctionnalité principale de l'objet. Vous pouvez toujours faire tout ce que vous pourriez faire pour un objet normal (tel que $obj->property ou $obj->method()). Ce qu'ils font cependant, c'est fournir la possibilité d'utiliser l'objet comme un tableau à certains endroits dans le noyau. Chacun fournit la fonctionnalité dans une portée stricte (ce qui signifie que vous pouvez utiliser n'importe quelle combinaison de ceux que vous souhaitez.Vous n'avez pas besoin d'être en mesure d'accéder à l'objet comme un tableau pour pouvoir le count()). C'est le vrai pouvoir ici.

Oh, et assigning the return value of new by reference is deprecated, donc il n'y a pas besoin de le faire ...

Alors, comme pour votre problème, voici une façon de le contourner. J'ai simplement implémenté l'interface ArrayAccess dans votre classe afin que $objFoo['reeb'] = 'roob'; fonctionne.

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]); 
     } 
    } 
} 

Ensuite, vous pouvez essayer votre code existant:

$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"; 
} 

Et il devrait fonctionner correctement:

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

Vous pouvez aussi "réparer" en changeant la propriété $objFoo->_array directement (juste comme OOP régulier). Il suffit de remplacer la ligne $objFoo['reeb'] = 'roob'; par $objFoo->_array['reeb'] = 'roob';. Cela va accomplir la même chose ....

  • Notez qu'il s'agit du comportement par défaut. Vous pouvez pirater un itérateur interne (l'itérateur retourné par IteratorAggreagate::getIterator) qui dépend de l'état des objets d'origine. Mais ce n'est pas la façon dont il est généralement fait
+0

Je suis confus. Je n'ai pas essayé d'écrire pendant l'itération; la boucle 'foreach' a été fermée avant' $ objFoo ['reeb'] = "roob"; ', mais cela donne quand même une erreur. Il aurait dû être réinitialisé à ce moment-là, n'est-ce pas? – user151841

+0

C'était. La chose est que vous essayez d'écrire à l'objet de la classe 'foo' comme un tableau. Cela ne fonctionne que si vous implémentez l'interface 'ArrayAccess' (qui active la syntaxe' $ obj [$ key] '). Sinon, il n'est pas activé. L'erreur que vous avez commise est parce que vous essayez de traiter '$ objFoo' comme un tableau, mais comme il n'a pas l'interface' ArrayAccess', il ne sait pas quoi faire, il s'agit donc d'erreurs fatales. Ajoutez l'interface 'ArrayAccess' à' foo' et implémentez les 4 méthodes requises. Ensuite, ça marchera bien ... Je vais éditer ma réponse avec un exemple ... – ircmaxell

+0

Alors, dans quels contextes pouvez-vous écrire à un 'IteratorAggregate'? – user151841