Question

Ai-je raison de penser que IteratorAggregate fournit seulement tableau comme lire accès à un objet? Si je dois écrire à l'objet-as-tableau, puis-je utiliser Iterator?

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
Était-ce utile?

La solution

Pour ce que ça vaut la peine, compte tenu de votre classe échantillon, vous auriez pu tout simplement 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

Autres conseils

Ok, maintenant que je comprends mieux ce que vous voulez dire, permettez-moi d'expliquer ici.

La mise en œuvre d'un iterator (soit via le Iterator ou interface IteratorAggregate) seulement ajouter des fonctionnalités lors de l'itération. Ce que cela signifie, est-il affecte uniquement la possibilité d'utiliser foreach. Il ne change pas, ou modifier toute autre utilisation de l'objet. Ni dont un soutien direct « écriture » au iterator. Je dis directement, puisque vous pouvez toujours écrire à l'objet de base tout en itérer, mais pas à l'intérieur du foreach.

Cela signifie que:

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

fonctionne très bien (car son écriture à l'objet d'origine).

Alors que

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

Très probablement ne fonctionnera pas car il est en train d'écrire à travers le iterator (qui ne sont pas directement pris en charge. Il peut être en mesure d'être fait, mais ce n'est pas la conception de base).

Pour voir pourquoi, le regard let à ce qui se passe dans les coulisses avec qui foreach ($obj as $value). Il est fondamentalement identique à:

Pour les classes de Iterator:

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

Pour les classes de IteratorAggregate:

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

Maintenant, vous voyez pourquoi l'écriture ne sont pas pris en charge? $iterator->current() ne renvoie pas de référence. Donc, vous ne pouvez pas écrire directement à l'aide foreach ($it as &$value).

Maintenant, si vous voulez faire cela, vous aurez besoin de modifier la iterator pour renvoyer une référence de current(). Toutefois, cela briserait l'interface (car il changerait la signature des méthodes). Donc, ce n'est pas possible. Cela signifie donc que l'écriture à la iterator n'est pas possible.

Cependant, sachez qu'il ne touche que l'itération elle-même. Cela n'a rien à voir avec l'accès à l'objet de tout autre contexte ou dans tout autre manoir. $obj->bar sera exactement la même chose pour un objet iterator comme pour un objet non iterator.

Maintenant, il est une différence entre la Iterator et IteratorAggregate. Retour sur les équivalences while, et vous pourrez peut-être voir la différence. Supposons que nous avons fait:

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

Que se passerait-il? Avec un Iterator, il imprime uniquement la première valeur. En effet, le iterator fonctionne directement sur l'objet pour chaque itération. Et puisque vous avez changé ce que les objets sur itère (le tableau interne), les changements d'itération.

Maintenant, si $objFoo est un IteratorAggregate, il imprimera 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 êtes revenu le new ArrayIterator($this->_array);. Ainsi, vous pouvez continuer à itérer sur tout l'objet tel qu'il était au début de l'itération.

Notez que la différence clé. Chaque itération d'un Iterator dépendra de l'état de l'objet à ce moment. Chaque itération d'un IteratorAggregate dépendra de l'état de l'objet au début de l'itération. * Est-ce logique?

Maintenant, pour votre erreur spécifique. Cela n'a rien à voir avec itérateurs du tout. Vous essayez d'accéder (écriture en fait) une variable en dehors de l'itération. Donc, il ne concerne pas l'itérateur du tout (à l'exception que vous essayez de vérifier les résultats par itérer).

Vous essayez de traiter l'objet comme un tableau ($objFoo['reeb'] = 'roob';). Maintenant, pour les objets normaux, ce n'est pas possible. Si vous voulez faire cela, vous devez mettre en œuvre le ArrayAccess Interface . Notez que vous ne devez pas avoir un itérateur défini afin d'utiliser cette interface. Tout ce qu'il fait est de fournir la capacité à des éléments spécifiques d'accès d'un objet à l'aide d'un tableau comme la syntaxe.

Notez que je dit tableau comme. Vous ne pouvez pas le traiter comme un tableau général. fonctions de sort ne marchera jamais. Cependant, il y a quelques autres interfacesvous pouvez mettre en œuvre pour rendre plus ensemble comme un objet. L'un est le Countable Interface qui permet la possibilité d'utiliser la fonction count() sur un objet (count($obj)).

Pour un exemple dans le noyau de combiner tous ces éléments dans une classe, consultez la page classe ArrayObject . Chaque interface dans cette liste offre la possibilité d'utiliser une fonctionnalité spécifique du noyau. Le IteratorAggregate offre la possibilité d'utiliser foreach. Le ArrayAccess offre la possibilité d'accéder à l'objet avec une syntaxe semblable à un tableau. L'interface Serializable permet de serialize et deserialize les données dans un manoir spécifique. L'interface permet de compter Countable l'objet comme un tableau.

Ainsi, ces interfaces n'affectent pas la fonctionnalité de base de l'objet de quelque façon. Vous pouvez toujours faire quelque chose avec ce que vous pourriez faire à un objet normal (comme $obj->property ou $obj->method()). Ce qu'ils font est cependant fournir la possibilité d'utiliser l'objet comme un tableau dans certains endroits dans le noyau. Chacune fournit la fonctionnalité dans un cadre strict (qui signifie que vous pouvez utiliser toute combinaison d'entre eux que vous voulez. Vous n'avez pas besoin d'être en mesure d'accéder à l'objet comme un tableau pour pouvoir count() il). C'est le vrai pouvoir ici.

Oh, et l'attribution de la valeur de retour de new par référence est obsolète , 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 mis en œuvre l'interface ArrayAccess dans votre classe afin que $objFoo['reeb'] = 'roob'; fonctionnera.

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 « fix » en modifiant directement la propriété $objFoo->_array (comme POO régulière). Il suffit de remplacer la ligne $objFoo['reeb'] = 'roob'; avec $objFoo->_array['reeb'] = 'roob';. Ça va faire la même chose ....

  • Notez que ceci est le comportement par défaut. Vous pouvez pirater ensemble un itérateur interne (le itérateur retourné par IteratorAggreagate::getIterator) qui ne dépend de l'état des objets d'origine. Mais ce n'est pas la façon dont il est généralement fait
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top