I'm having troubles building a deeply nested associative array in PHP. From the questions/answers I've seen here and there, I gathered I should use references but I just can't figure out how to do so.
I am using PHP 5.3
I'm parsing a file that looks like JSON. It contains nested "sections" enclosed in curly braces and I want to build up a tree representation of the file using nested associative arrays.
I'm starting with a root section and a "current section" variables:
$rootSection = array();
$currentSection = $rootSection;
$sections = array();
When I enter a new section ('{'), this is what I do:
$currentSection[$newSectionName] = array();
array_push($sections, $currentSection);
$currentSection = $currentSection[$newSectionName];
I use the $sections variable to pop out of a section ('}') into its parent one:
$currentSection = array_pop($sections);
And finally, when I want to add a property to my section, I basically do:
$currentSection[$name] = $value;
I've removed all attempt to use references from the above code, as nothing has worked so far...
I might as well say that I am used to Javascript, where references are the default...
But it's apparently not the case with PHP?
I've dumped my variables in my parsing code and I could see that all properties were correctly added to the same array, but the rootSection array or the one pushed inside $sections would not be updated identically.
I've been looking for a way to do this for a few hours now and I really don't get it...
So please share any help/pointers you might have for me!
UPDATE: The solution
Thanks to chrislondon I tried using =& again, and managed to make it work.
Init code:
$rootSection = array();
$currentSection =& $rootSection;
$sections = array();
New section ('{'):
$currentSection[$newSectionName] = array();
$sections[] =& $currentSection;
$currentSection =& $currentSection[$newSectionName];
Exiting a section ('}'):
$currentSection =& $sections[count($sections) - 1];
array_pop($sections);
Note that starting around PHP 5.3, doing something like array_push($a, &$b); is deprecated and triggers a warning. $b =& array_pop($a) is also not allowed; that's why I'm using the []=/[] operators to push/"pop" in my $sections array.
What I initially had problems with was actually this push/pop to my sections stack, I couldn't maintain a reference to the array and was constantly getting a copy.
Thanks for your help :)
If you want to pass something by reference use =& like this:
$rootSection = array();
$currentSection =& $rootSection;
$currentSection['foo'] = 'bar';
print_r($rootSection);
// Outputs: Array ( [foo] => bar )
I've also seen the syntax like this $currentSection = &$rootSection; but they're functionally the same.
Related
I want to initialize array(in my case it's multidimensional) and I want to retrieve reference to a separate variable so i could access it via that variable.
For example to achieve this im writing two lines
$multidimensional[$some_key] = array();
$item = &$multidimensional[$some_key];
This thing works just fine, but if I wanted to do this in one like I had tried:
$item = &$multidimensional[$some_key] = array(); // syntax error
Question is there a way to do this in single line?
How about:
$item = &($multidimensional[$some_key] = array());
?
given the following code:
$tree = array();
$node =& $tree[];
// imagine tons of code that populates $tree here
how can i entirely delete the ZVAL $node points to by reference? Is that even possible?
Using unset(), only the reference is destroyed and not the node in $tree itself:
unset($node);
print_r($tree);
// outputs:
Array
(
[0] =>
)
I know this is the expected behaviour of unset($reference) and I also know how the ZVAL refcounter works.
But i really need to delete that node after processing in a specific corner case.
Can i somehow find the correct array index and unset the array element directly like unset($tree[$node_index])?
Disclaimer: The above example is minified and isolated. Actually i'm modifying a complex parser for a really ugly nested table data structure that is presented as a stream. The code heavily uses pointers as backreferences and i'd like to avoid refactoring the whole code.
If you grab a reference to an array element and unset the reference the array will not be affected at all -- that's just how unset works, and this behavior is not negotiable.
What you need to do is remember the key of the element in question and unset directly on the array afterwards:
$tree = array();
$tree[] = 'whatever';
end($tree);
$key = key($tree);
// ...later on...
unset($tree[$key]);
Of course this is extremely ugly and it requires you to keep both $tree (or a reference to it) and $key around. You can mitigate this somewhat by packaging the unset operation into an anonymous function -- if there's a good chance you are going to pull the trigger later, the convenience could offset the additional resource consumption:
$tree = array();
$tree[] = 'whatever';
end($tree);
$key = key($tree);
$killThisItem = function() use(&$tree, $key) { unset($tree[$key]); } ;
// ...later on...
$killThisItem();
I have an array containing several keys, values, objects etc.. I need to empty that array but I'd like to do it in the most efficient manner.
The best I can come up with is:
foreach ($array as $key => $val) unset($array[$key]);
But I don't like the idea of having to loop through the array to just empty it.. surely there's a nice slick/clever way of doing this without wasting memory creating a new array?
Note: I'm not sure myself if it does cost extra memory in creating the array as new again. If it doesn't then $array = new array(); would be a fine way of 'emptying' it.
Just try with:
$array = array();
It highly depends on what you mean.
To empty the current reference you can always do
$array = array();
To completely remove the current instance from the scope
unset($array);
Unfortunately both of these cases don't necessarily mean the memory associated with each element is released.
PHP works with something called "references" for your variables. Your variables are actually labels or references pointing to data, not the actual container for data.
The PHP garbage collector can offer more insight on this subject.
Now take a look at this example, taken from the docs:
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );# a: (refcount=3, is_ref=0)='new string'
unset( $b, $c );
xdebug_debug_zval( 'a' );# a: (refcount=1, is_ref=0)='new string'
This unfortunately applies to all your variables. Including arrays. Cleaning up the memory associated with the array is a whole different subject I'm afraid.
I've noticed a longer discussion in the comments regarding using unset() on each individual key.
This feels like extremely bad practice. Consider the following code:
class A{
function __construct($name){$this->name=$name;}
function __destruct(){echo $this->name;}
}
$a=array();
$b=array();
$c=array();
for($i=0;$i<5;$i++) {
$a[]=new A('a');
$b[]=new A('b');
$c[]=new A('c');
}
unset($a);
$b=array();
echo PHP_EOL.'done'.PHP_EOL;
This will output:
aaaaabbbbb
done
ccccc
When the reference to a particular data structure reaches 0, it is cleaned from memory.
Both =array() and unset will do the same thing.
Now if you don't actually need array() you can use null :
$array=null;
This keeps the label in memory, but removes the reference it held to any particular data.
It's simple:
$array = array();
$array will be existing and type of array (but empty), and your data can be garbaged later from memory.
Well... why not: $array = array(); ?
As Suresh Kamrushi pointed out, I could use array_keys:
foreach (array_keys($array) as $key) unset($array[$key]);
This is probably the nicest solution for now.. but I'm sure someone will come up with something better soon :)
Try this:
// $array is your original array
$array = array_combine( array_keys( $array ), array_fill( 0, count($array), 0 ) );
The above will blank your array keeping the keys intact.
Hope this helps.
I made this object array
$trailheads[] = new StdClass;
Then I was putting an object in there each time it looped
$trailheads[] = $trailhead;
Then I returned the whole thing from the function like this:
$ret->ok = empty($errors);
$ret->errors = $errors;
$ret->o = $trailheads;
return $ret;
Once I got the values back, and loop through the results in the trailheads[] I keep looping 3 times instead of the expected 2. Is there a reason an extra iteration might be happening?
Here is how I try to loop:
foreach ($trailhead_list->o as $key)
{
$objkey = (object) $key;
echo '<p><h3>Trailhead Name: '.$objkey->trailhead_name.'</h3></p>';
}
After this:
$trailheads[] = new StdClass;
your $trailheads array contains on element, an instance of StdClass.
Then you add more elements, but there will be that first one.
Maybe you wanted to initialize the $trailheads[] like that, but instead you gave value to one of the array elements. Use this instead:
$trailheads = array();
This:
I made this object array
$trailheads[] = new StdClass;
... appears to be the source of the extra array element. Instantiate an array empty like this:
$trailheads = array();
Then loop and add objects:
$trailheads[] = $trailhead;
Also a note: I am guessing you don't have notices or warnings enabled. Turn them on while you develop. The line $trailheads[] = new StdClass; would have generated a notice, since you are adding a new element to a variable that hasn't been declared as an array (or anything else). PHP's loose typing is very forgiving, but this line would have generated a notice.
Warnings and notices on while you develop, turn them off for production. It will help you avoid common bugs and it will make you a better programmer to boot.
In the line:
$trailheads[] = new StdClass;
You are assigning a new StdClass object into the $trailheads array. That is to say you aren't declaring it as a variable but actually adding an element.
Suppose I have an array of nodes (objects). I need to create a duplicate of this array that I can modify without affecting the source array. But changing the nodes will affect the source nodes. Basically maintaining pointers to the objects instead of duplicating their values.
// node(x, y)
$array[0] = new node(15, 10);
$array[1] = new node(30, -10);
$array[2] = new node(-2, 49);
// Some sort of copy system
$array2 = $array;
// Just to show modification to the array doesn't affect the source array
array_pop($array2);
if (count($array) == count($array2))
echo "Fail";
// Changing the node value should affect the source array
$array2[0]->x = 30;
if ($array2[0]->x == $array[0]->x)
echo "Goal";
What would be the best way to do this?
If you use PHP 5:
Have you run your code? It is already working, no need to change anything. I get:
Goal
when I run it.
Most likely this is because the values of $array are already references.
Read also this question. Although he OP wanted to achieve the opposite, it could be helpful to understand how array copying works in PHP.
Update:
This behaviour, when copying arrays with objects, the reference to the object is copied instead the object itself, was reported as a bug. But no new information on this yet.
If you use PHP 4:
(Why do you still use it?)
You have to do something like:
$array2 = array();
for($i = 0; $i<count($array); $i++) {
$array2[$i] = &$array[$i];
}
it is some time that I don' write PHP code, but does the code
// Some sort of copy system
$array2 = $array;
actually work?
Don't you have to copy each element of the array in a new one?