Jump to content

[SOLVED] SPL Recursive Iterators offsetUnset


periklis

Recommended Posts

Hi everyone,

I've been using SPL Iterators for some time and I've run onto this problem: I use nested arrays for tree-like data representations. For example, take this "family tree":

$array = array('id' => 1, 
               'name' => 'john', 
               2 => array('id' => 2, 'name' => 'michael'),
               32 => array('id' => 32, 'name' => 'mary'),
               14 => array('id' => 14, 'name' => 'george',75 => array('id' => 5, 'name' => 'chris'),9 => array('id' => 9, 'name' => 'chris')));

I'm trying to unset a specific tree node, using the code below:

$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator :: SELF_FIRST);   //Create recursive iterator for the array
$iterator -> rewind();        //Initialize the iterator
while ($iterator -> valid() && $iterator -> key() != 75) {
    $iterator -> next();                                                   //Advance the iterator until you reach the designated node
}
$iterator -> offsetUnset('id');                                        //Unset the 'id' array member

Now, if we print the subiterator, the array member with key 'id' is unset:

print_r($iterator -> getSubIterator());                             //Does not contain the 'id' entry

But it is not unset from the array itself:

print_r($array);                                                        //The array is the same as before, nothing has changed

 

Any ideas why is that happening or what else can I do?

Thanx in advance!

Link to comment
Share on other sites

The only thing I can think of based off of your code is that you're not passing by reference. Your unset function may just be altering a copy of a part of that array, not the array itself.

Just look into references on the manual and see if that can help you.

Random examples:

//Function arguments
function by_value($value) {
$value=5;
}
function by_ref(&$value) {
$value=5;
}
$x=12;
by_value($x); //$x is 12
by_ref($x); //$x is 5

//Function return
global $test='Hello';
function return_value() {
global $test;
return $test;
}
function& return_ref() {
global $test;
return $test;
}
$x=return_value();
$x='World'; //$x is 'World', $test is still 'Hello'
$x=return_ref();
$x='People'; //$x and $test are 'People'

Link to comment
Share on other sites

Hi and thanks for your reply.

Ok, using references seems a good idea, but where would I put the reference? I mean, the iterator has an internal pointer to the array, I don't think I have any ability to interfere with it (especially since I don't use any custom function to traverse the nested arrays).

Any help would be appreciated!

 

 

Link to comment
Share on other sites

Nope, this doesn't work, it throws a parse error (reasonable enough since you can't do $array = & array() just as you wouldn't do $temp = &1;

I guess that even if RecursiveArrayIterator uses a reference for the array, it reinitializes the inner iterator each time it traverses a subarray and does not use references deeper (which seems to explain why it works when unseting values of the "root" array, but not subarrays).

Thanks alot anyway, I guess I'll just have to figure out a "counter-intuitive awful-looking impossible-to-maintain" workaround for this...

Link to comment
Share on other sites

Sorry, I was thinking that you had designed the iterator object...

I'm not sure how you can change the code so it directly references to your original array variable.

What you might want to do is just see if you can get the data from the iterator, something similar to getSubIterator()... Maybe current() or getArrayCopy().

I'm looking at the reference here.

 

Link to comment
Share on other sites

Yes, I've read through this reference (along with many other, the best one beeing http://www.phpro.org/tutorials/Introduction-to-SPL.html

Last night however, I figured out a way to do it: You have to declare all arrays and suvbarrays as RecursiveArrayIterator objects

new RecursiveArrayIterator(array('id'   => 1, 
		   'name' => 'john', 
		   2      => new RecursiveArrayIterator(array('id'   => 2, 
                           	   'name' => 'michael')),
etc...

 

This way it seems to work, but I can't tell because apache keeps crashing everytime I try to print the array after the offsetUnset() call... I'll look into it more thoroughly and keep you up to date :)

PS: The same problem holds not for just unsetting, but for performing any update on the subarrays data (for example, you have no way to change the 'name' property of a subarray through the iterator)

 

Link to comment
Share on other sites

Well, finally I came up with a solution: you must declare each subarray to be ArrayObject:

$array = new RecursiveArrayIterator(array('id'   => 1, 
		   'name' => 'john', 
		   2      => new ArrayObject(array('id'   => 2, 
                           	   'name' => 'michael')),
		   32     => new ArrayObject(array('id'   => 32, 
                               'name' => 'mary')),
		   14     => new ArrayObject(array('id'   => 14, 
                          	   'name' => 'george',
                               75    => new ArrayObject(array('id' => 5, 'name' => 'chris')),
                               9     => new ArrayObject(array('id' => 9, 'name' => 'john')))
                         )));

 

This way, unsetting a value through the iterator works (and without crashing the web server :))

Thanks for all you help once more guys!

Link to comment
Share on other sites

Well, not sure what more I can add, so I'll just state why it works for further understanding. You probably already know this, but I'm just avoiding doing homework now.  ;D

 

When arrays are copied (i.e. assigning $test=$array), it goes through the array and copies all primitive data over. Primitive data types are numbers, strings, references, object references, and arrays (fairly certain that they are - subarrays should be copied). Since they are copied and not referenced, changing the primitive data in one array will not affect the other copy.

Note: Object references only refer to objects, and they are the only way to refer to objects. When an object reference is copied, it does not copy/clone the object. That has to be done explicitly. [Applies to PHP 5 only]

 

Now, if you change data that is referenced in one array, say something in an object, that change will be visible through the reference in the other array, since they're both references.

 

That's what you are doing with ArrayObject - the iterator returns references to the original object instantiated in the original array, therefore the change is visible through the reference in that array.

 

Here's just some random examples:

//Basics
$array=array(5,15, array(12,7,"pie"), "biscuits", new stdClass());
$test=$array; //All primitives copied
$test[1]=18; //was 15 - This does not change the original value in $array
$test[2][1]++; //8
$test[3].=" are delicious";
$test[4]->name="Bob";
//Now print out the arrays. All primitive data in $array should remain unchanged. The only different thing will be the stdClass, which will now have the 'name' variable
print_r($array);
echo "<br>";
print_r($test);
echo "<br><br>";

//Try a different approach, same operations
$ref=&$array; //referenced
$ref[1]=18; //Just wait for it...
$ref[2][1]++;
$ref[3].=" are delicious";
$ref[4]->color="red";
//Print out - all data should be the same
print_r($array); echo "<br>"; print_r($ref); echo "<br><br>";

//And just to screw around
$x=15;
$array=array($x,"apple",&$x,new stdClass(), array("sub",new stdClass()) );
$test=$array; //Old data in $test left to garbage collector
$test[0]=12;
$test[1].=" cake";
$test[2]+=58;
$test[3]->name="Timmy";
$test[4][0].="array";
$test[4][1]->data=array("good",2,"go");
//Print
echo "$x<br>"; print_r($array); echo "<br>"; print_r($test);

 

Output:

Array ( [0] => 5 [1] => 15 [2] => Array ( [0] => 12 [1] => 7 [2] => pie ) [3] => biscuits [4] => stdClass Object ( [name] => Bob ) )
Array ( [0] => 5 [1] => 18 [2] => Array ( [0] => 12 [1] => 8 [2] => pie ) [3] => biscuits are delicious [4] => stdClass Object ( [name] => Bob ) )

Array ( [0] => 5 [1] => 18 [2] => Array ( [0] => 12 [1] => 8 [2] => pie ) [3] => biscuits are delicious [4] => stdClass Object ( [name] => Bob [color] => red ) )
Array ( [0] => 5 [1] => 18 [2] => Array ( [0] => 12 [1] => 8 [2] => pie ) [3] => biscuits are delicious [4] => stdClass Object ( [name] => Bob [color] => red ) )

73
Array ( [0] => 15 [1] => apple [2] => 73 [3] => stdClass Object ( [name] => Timmy ) [4] => Array ( [0] => sub [1] => stdClass Object ( [data] => Array ( [0] => good [1] => 2 [2] => go ) ) ) )
Array ( [0] => 12 [1] => apple cake [2] => 73 [3] => stdClass Object ( [name] => Timmy ) [4] => Array ( [0] => subarray [1] => stdClass Object ( [data] => Array ( [0] => good [1] => 2 [2] => go ) ) ) ) 

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.