2009-08-04 7 views
14

Je pense que je fais quelque chose de stupide, mais je suis confus par ce qui semble être un problème simple avec SPL:Comment modifier les clés et les valeurs de tableau lors de l'utilisation d'un objet RecursiveArrayIterator?

Comment puis-je modifié le contenu d'un tableau (les valeurs dans cet exemple), en utilisant un RecursiveArrayIterator/RecursiveIteratorIterator? En utilisant le code de test suivant, je peux modifier la valeur dans la boucle en utilisant getInnerIterator() et offsetSet(), et vider la matrice modifiée pendant que je suis dans la boucle. Mais quand je quitte la boucle et vide la matrice de l'itérateur, je reviens aux valeurs d'origine. Que ce passe-t-il?

$aNestedArray = array(); 
$aNestedArray[101] = range(100, 1000, 100); 
$aNestedArray[201] = range(300, 25, -25); 
$aNestedArray[301] = range(500, 0, -50); 

$cArray = new ArrayObject($aNestedArray); 
$cRecursiveIter = new RecursiveIteratorIterator(new RecursiveArrayIterator($cArray), RecursiveIteratorIterator::LEAVES_ONLY); 

// Zero any array elements under 200 
while ($cRecursiveIter->valid()) 
{ 
    if ($cRecursiveIter->current() < 200) 
    { 
     $cInnerIter = $cRecursiveIter->getInnerIterator(); 
     // $cInnerIter is a RecursiveArrayIterator 
     $cInnerIter->offsetSet($cInnerIter->key(), 0); 
    } 

    // This returns the modified array as expected, with elements progressively being zeroed 
    print_r($cRecursiveIter->getArrayCopy()); 

    $cRecursiveIter->next(); 
} 

$aNestedArray = $cRecursiveIter->getArrayCopy(); 

// But this returns the original array. Eh?? 
print_r($aNestedArray); 
+0

on dirait que c'est un bug, vous devez déposer sur http://bugs.php.net/ – null

Répondre

2

On dirait getInnerIterator crée une copie du sous-iterator.

Peut-être qu'il existe une méthode différente? (Restez à l'écoute ..)


Mise à jour: après le piratage à elle pendant un certain temps, et en tirant dans 3 autres ingénieurs, il ne ressemble pas à PHP vous donne un moyen de modifier les valeurs de la subIterator.

Vous pouvez toujours utiliser l'ancien stand de:

<?php 
// Easy to read, if you don't mind references (and runs 3x slower in my tests) 
foreach($aNestedArray as &$subArray) { 
    foreach($subArray as &$val) { 
     if ($val < 200) { 
      $val = 0; 
     } 
    } 
} 
?> 

OU

<?php 
// Harder to read, but avoids references and is faster. 
$outherKeys = array_keys($aNestedArray); 
foreach($outherKeys as $outerKey) { 
    $innerKeys = array_keys($aNestedArray[$outerKey]); 
    foreach($innerKeys as $innerKey) { 
     if ($aNestedArray[$outerKey][$innerKey] < 200) { 
      $aNestedArray[$outerKey][$innerKey] = 0; 
     } 
    } 
} 
?> 
+1

Je ne pense pas que ce soit aussi simple que getInnerIterator créant une copie, puisque '$ cRecursiveIter -> getArrayCopy() 'dans la boucle donne la valeur modifiée –

0

Je sais que cela ne répond pas à votre question directement, mais ce n'est pas une bonne pratique de modifier l'objet sous itération tout en itérant au-dessus.

+4

Are yo Tu es sûr de ça? Les docs 'RecursiveArrayIterator' disent:" Cet itérateur permet d'annuler et de modifier les valeurs et les clés lors de l'itération sur les tableaux et les objets ... ". –

0

Est-ce que cela pourrait se résumer à passer par la référence par rapport à la valeur?

Par exemple essayez de changer:

$cArray = new ArrayObject($aNestedArray); 

à:

$cArray = new ArrayObject(&$aNestedArray); 
+0

Non, cela n'aide pas. Pour ce que cela vaut, l'utilisation d'une valeur foreach de référence n'est pas autorisée pour les itérateurs, ce qui provoque une erreur fatale: foreach ($ cRecursiveIter as & $ iVal) ' –

5

Il semble que les valeurs dans des tableaux simples ne sont pas modifiables car ils ne peuvent pas être passés par référence au constructeur de ArrayIterator (RecursiveArrayIterator hérite de ses méthodes offset*() de cette classe, voir SPL Reference). Donc, tous les appels à offsetSet() fonctionnent sur une copie du tableau. Je suppose qu'ils ont choisi d'éviter l'appel par référence parce que cela n'a pas beaucoup de sens dans un environnement orienté objet (c'est-à-dire lors du passage d'instances de ArrayObject qui devrait être le cas par défaut).

code plus pour illustrer ceci:

$a = array(); 

// Values inside of ArrayObject instances will be changed correctly, values 
// inside of plain arrays won't 
$a[] = array(new ArrayObject(range(100, 200, 100)), 
      new ArrayObject(range(200, 100, -100)), 
      range(100, 200, 100)); 
$a[] = new ArrayObject(range(225, 75, -75)); 

// The array has to be 
//  - converted to an ArrayObject or 
//  - returned via $it->getArrayCopy() 
// in order for this field to get handled properly 
$a[] = 199; 

// These values won't be modified in any case 
$a[] = range(100, 200, 50); 

// Comment this line for testing 
$a = new ArrayObject($a); 

$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($a)); 

foreach ($it as $k => $v) { 
    // getDepth() returns the current iterator nesting level 
    echo $it->getDepth() . ': ' . $it->current(); 

    if ($v < 200) { 
     echo "\ttrue"; 

     // This line is equal to: 
     //  $it->getSubIterator($it->getDepth())->offsetSet($k, 0); 
     $it->getInnerIterator()->offsetSet($k, 0); 
    } 

    echo ($it->current() == 0) ? "\tchanged" : ''; 
    echo "\n"; 
} 

// In this context, there's no real point in using getArrayCopy() as it only 
// copies the topmost nesting level. It should be more obvious to work with $a 
// itself 
print_r($a); 
//print_r($it->getArrayCopy());
3

Ne pas utiliser les classes iterator (qui semblent être copie données sur le RecursiveArrayIterator::beginChildren() au lieu de passer par référence.)

Vous pouvez utiliser ce qui suit pour obtenir ce que vous voulez

function drop_200(&$v) { if($v < 200) { $v = 0; } } 

$aNestedArray = array(); 
$aNestedArray[101] = range(100, 1000, 100); 
$aNestedArray[201] = range(300, 25, -25); 
$aNestedArray[301] = range(500, 0, -50); 

array_walk_recursive ($aNestedArray, 'drop_200'); 

print_r($aNestedArray); 

ou utiliser create_function() au lieu de créer la fonction drop_200, mais votre kilométrage peut varier selon l'utilisation de create_function et de la mémoire.

+0

Juste gaspillé une heure avec ces sanglantes classes SPL Iterator - probablement pas pour la première fois - quand 'array_walk_recursive' fait le tour parfaitement, et avec moins d'instanciation/LOC. Il y a une leçon là-bas je suppose ... En tout cas, merci! –

+0

Cela semble être l'entrée DB de bogue liée: https://bugs.php.net/bug.php?id=68682 – powtac

+0

Ce n'est pas la solution la plus idéale, car en récursive, vous ne pouvez pas vérifier le type de données des éléments . Deuxièmement, il vaut mieux utiliser l'itérateur, car c'est plus oop. – schellingerht

1

Vous devez appeler getSubIterator à la profondeur actuelle, utilisez offsetSet à cette profondeur, et faites de même pour toutes les profondeurs remontant l'arbre. Ceci est vraiment utile pour effectuer des remplacements et des fusions de tableaux de niveau illimités, sur des tableaux ou des valeurs dans des tableaux. Malheureusement, array_walk_recursive ne fonctionnera pas dans ce cas car la fonction ne visite que les nœuds feuille .. donc la clé 'replace_this_array' dans le tableau $ ci-dessous ne sera jamais visitée.

À titre d'exemple, pour remplacer toutes les valeurs dans un tableau des niveaux inconnus en profondeur, mais seulement ceux qui contiennent une certaine clé, vous devez faire ceci:

$array = [ 
    'test' => 'value', 
    'level_one' => [ 
     'level_two' => [ 
      'level_three' => [ 
       'replace_this_array' => [ 
        'special_key' => 'replacement_value', 
        'key_one' => 'testing', 
        'key_two' => 'value', 
        'four' => 'another value' 
       ] 
      ], 
      'ordinary_key' => 'value' 
     ] 
    ] 
]; 

$arrayIterator = new \RecursiveArrayIterator($array); 
$completeIterator = new \RecursiveIteratorIterator($arrayIterator, \RecursiveIteratorIterator::SELF_FIRST); 

foreach ($completeIterator as $key => $value) { 
    if (is_array($value) && array_key_exists('special_key', $value)) { 
     // Here we replace ALL keys with the same value from 'special_key' 
     $replaced = array_fill(0, count($value), $value['special_key']); 
     $value = array_combine(array_keys($value), $replaced); 
     // Add a new key? 
     $value['new_key'] = 'new value'; 

     // Get the current depth and traverse back up the tree, saving the modifications 
     $currentDepth = $completeIterator->getDepth(); 
     for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) { 
      // Get the current level iterator 
      $subIterator = $completeIterator->getSubIterator($subDepth); 
      // If we are on the level we want to change, use the replacements ($value) other wise set the key to the parent iterators value 
      $subIterator->offsetSet($subIterator->key(), ($subDepth === $currentDepth ? $value : $completeIterator->getSubIterator(($subDepth+1))->getArrayCopy())); 
     } 
    } 
} 
return $completeIterator->getArrayCopy(); 
// return: 
$array = [ 
    'test' => 'value', 
    'level_one' => [ 
     'level_two' => [ 
      'level_three' => [ 
       'replace_this_array' => [ 
        'special_key' => 'replacement_value', 
        'key_one' => 'replacement_value', 
        'key_two' => 'replacement_value', 
        'four' => 'replacement_value', 
        'new_key' => 'new value' 
       ] 
      ], 
      'ordinary_key' => 'value' 
     ] 
    ] 
]; 
+0

Ne résout pas le problème pour moi. – schellingerht

1

Convertir le tableau à un objet premier et fonctionne comme prévu ..

$array = [ 
     'one' => 'One', 
     'two' => 'Two', 
     'three' => [ 
      'four' => 'Four', 
      'five' => [ 
       'six' => 'Six', 
       'seven' => 'Seven' 
      ] 
     ] 
    ]; 

    // Convert to object (using whatever method you want) 
    $array = json_decode(json_encode($array)); 

    $iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array)); 
    foreach($iterator as $key => $value) { 
     $iterator->getInnerIterator()->offsetSet($key, strtoupper($value)); 
    } 

    var_dump($iterator->getArrayCopy()); 
+0

Merci! Cela marche! – schellingerht

Questions connexes