PHP Array reference replacement - php

I have following code
public function index() {
$a = 2;
$b = 8;
$arr[] = $a;
$arr[] = &$a;
$arr[] = $a;
$this->dmp($arr);
$arr[1] = $b;
$this->dmp($arr);
$a++;
$b++;
$this->dmp($arr);
}
private function dmp($val) {
echo '<pre>';
var_dump($val);
echo '</pre>';
}
which gives me following result
array(3) {
[0]=>
int(2)
[1]=>
&int(2)
[2]=>
int(2)
}
array(3) {
[0]=>
int(2)
[1]=>
&int(8)
[2]=>
int(2)
}
array(3) {
[0]=>
int(2)
[1]=>
&int(9)
[2]=>
int(2)
}
Q: Why the value on index 1 is not replaced with value of $b variable, but there is still the reference? The reference remains even after calling $arr[1] = $b; without the appersand?

Here is a simple explanation:
$arr[0] = $a; // Index 0 holds the same value as $a which is 2
$arr[1] = &$a; // Index 1 holds a reference to variable $a
$arr[2] = $a; // Index 2 holds the same value as $a which is 2
You then instruct the code to change the value of the variable referenced by index 1, to hold the same value as $b which is 8.
$arr[1] = $b; // Index 1 references variable $a so $a will become 8
If you do var_dump($a) now, you will see that $a is 8.
You then increment both $a and $b, which at this point have the same value. They both will now have the value 9
The contents of the array at the end are the same as initially specified. The only difference is that the variable which is referenced by element with index 1 has now a different value.
$arr[0] = 2; // Index 0 holds the value 2
$arr[1] = &$a; // Index 1 holds a reference to variable $a which has a value 9 now
$arr[2] = 2; // Index 2 holds the value 2

When you assign by reference it creates a link between the two, they are pointing to the same result. So when you make another assignment it also assigns to the referenced variable. If you inspect $a you will see it changes to 8 and then 9. To break the reference use unset().
Demo
This $arr[] = &$a; says make $arr[1] point to the same data as $a.
This $arr[1] = $b; says assign the value of $b to $arr[1], which is 8, and $a is linked to $arr[1] so $a is 8 as well.
Check What References Do

Related

PHP - foreach lose reference with null coalescing operator

Q1:
I think the ?? will do nothing when:
$a = [1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
But why?
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
Q2:
This is more strange:
foreach ($a = [1, 2] as &$v) {
$v++;
}
var_dump($a);
// output
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
My thinking:
I think the expressions are not referencable, but foreach catch the error or somehow and then make a copy.
References that work:
$a = 1;
$c = &$a;
Do not work:
$a = 1;
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);
Dos ?? make a copy? I just don't want to wrap the foreach with a if (isset($a)) if $a is null and foreach will fail.
TL;DR For your case, you could consider using the null coalesce operator in this manner:
$a = $a ?? [];
foreach ($a as &$v) { ... }
Or, don't use references at all, by either using array_map() or by using the keys to make modifications in the underlying array.
Q1
$a = [1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
The coalesce operator uses a copy of the original array, and then applies the right hand operand if null. Therefore, the iteration happens over a copy of the original array.
You could compare this to the following:
$a = [1, 2];
$x = $a ?? [];
$x[1] = 4;
var_dump($a); // [1, 2]
Code Insight
compiled vars: !0 = $a, !1 = $v
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
8 0 E > ASSIGN !0, <array>
9 1 COALESCE ~3 !0
2 QM_ASSIGN ~3 <array>
3 > FE_RESET_RW $4 ~3, ->8
... rest of looping code
The first operand of FE_RESET_RW is the hash variable that will be iterated over, and you can see that it's ~3 instead of !0 ($a in your code), which is what you expected to happen.
Q2
foreach ($a = [1, 2] as &$v) {
$v++;
}
What happens here is that the return value of the assignment $a = [1, 2] gets used as the array to iterate over.
You can compare this behaviour to something like this:
$x = $a = [1, 2];
$x[0] = 4; // modify in-place
var_dump($a); // [1, 2]
Code Insight
compiled vars: !0 = $a, !1 = $v
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > ASSIGN $2 !0, <array>
1 > FE_RESET_RW $3 $2, ->6
... rest of looping code
Again, $2 is the first operand of FE_RESET_RW, which is the assignment result, and so iteration will not happen against !0 ($a in your code).
You can use the expanded array syntax to get the index, and then use that to dereference the original array value:
$a = [1, 2];
foreach ($a ?? [] as $i => $v) {
++$a[$i];
}
var_dump($a);
But note this is likely useless anyway, because if $a isn't set (so that the ?? qualifies) then the loop will make zero iterations and $a will still be unset for the var_dump(). (Unless that's what you need, I suppose...)
I just dont want to wrap the foreach with a if (isset($a)) if $a is null and foreach will fail.
This is unavoidable if you haven't initialized variables to the correct type, but here's some trickery in a utility function using pass by reference, return by reference and a default value:
function &test(&$array=[]) {
return $array;
}
$a = [1, 2];
foreach (test($a) as &$v) {
$v++;
}
Doesn't generate errors if $a is not set and doesn't loop, however in the above it yields:
array(2) {
[0]=>
int(2)
[1]=>
&int(3)
}
In PHP arrays are assigned by value (assignment copies), so that if $a !== null then $a ?? [] returns the value of $a or [1, 2]. So $a is not modified by the reference to the values of this value using &$v.
Objects are assigned by reference so a reference is returned in this case and the original object is modified, unless $a is not set. Then you will obviously get:
Notice: Undefined variable: a
$a = (object)[1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
This yields:
object(stdClass)#1 (2) {
["0"]=>
int(2)
["1"]=>
&int(3)
}
From Assignment by Reference:
Assignment by reference is also supported, using the "$var = &$othervar;" syntax. Assignment by reference means that both variables end up pointing at the same data, and nothing is copied anywhere.
In these cases, they are all expressions and cannot be referenced:
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);
If this is just an exercise that's fine, but if you are trying to solve a specific problem then you need to outline that broader problem.

Assign by reference PHP misunderstaning

Whats the meaning of $arr[0] result "2" in the code given below, $arr2 is copying $arr and increasing its first value by one, so the result of $arr2[0] "2" is understaning,but whats happening with $arr, when i pass by reference $arr[0] to $a like so $a=&$arr[0] the result of $arr[0] is 2, when i pass it by value $a=$arr[0[ the result of $arr[0] would be set to 1 as it should, can anyone enlighten me on this?
<?php
$a = 1;
$arr = array(1);
$a = &$arr[0];
$arr2 = $arr;
$arr2[0]++;
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
?>
References in PHP are not like pointers; when you assign or pass something by reference, you create what we might call a "reference set" where both variables are references to the same "zval" (the structure in memory which holds the type and value of a variable).
An important consequence of this is that references are symmetrical. I tend to write assign by reference as $foo =& $bar rather than $foo = &$bar to emphasise this: the operator doesn't just take a reference to $bar and put it into $foo, it affects both operands.
With that in mind, let's go through your example:
$arr = array(1);
This will create two zvals: one for the array $arr itself, and one for the value in position 0 of that array ($arr[0]).
$a =& $arr[0];
This binds $a and $arr[0] into a reference set. Whichever of those names we use, we will be using the same zval that was previously created, and currently holds 1.
$arr2 = $arr;
This copies the contents of $arr into a new array zval, $arr2. But it doesn't resolve the references inside that array into values, it just copies the fact that they are a reference.
So $arr2[0] is now also part of the reference set containing $arr[0] and $a.
$arr2[0]++;
This increments the zval pointed to by our reference set, from 1 to 2.
echo $arr[0]. "<br>";//2
echo $arr2[0]. "<br>";//2
Since these are both part of the same reference set, they both point to the same zval, which is integer 2.
The inevitable question is, "bug or feature?" A more useful example would be passing an array containing references into a function:
function foo($array) {
$array[0] = 42;
$array[1] = 69;
}
$a = 1;
$b = 1;
$foo = [ &$a, &$b ];
foo($foo);
echo $a; // 42
echo $b; // 69
Like assignment, passing into the function didn't break the references, so code manipulating them can safely be re-factored into multiple functions.
Not sure if this is a bug but var_dump() can help to explain.
<?php
$a = 1;
$arr = array(1);
var_dump( $arr );
$a = &$arr[0];
var_dump( $arr );
$arr2 = $arr;
$arr2[0]++;
Output:
array(1) {
[0]=>
int(1)
}
array(1) {
[0]=>
&int(1)
}
Take note of &int(1) in the second var_dump(). This tells us that position #0 of $arr has been turned into a reference pointer to a position in PHP's memory instead of remaining a dedicated value.
So when you perform $arr2 = $arr;, $arr2 receives that reference pointer as well.

Why are variables not changed and objects are after assignment in a function?

I found that a common var $g can't be changed in a function; but object $e can be. Why?
<?php
class e {
public $var1 = 1;
}
function f($e,$g) {
$e->var1 = 2;
$f = 2;
}
$e = new e;
$g = 1;
var_dump($e->var1);
var_dump($g);
f($e,$g);
var_dump($e->var1);
var_dump($g);
result:
int(1)
int(1)
int(2)
int(1)
Objects are passed and assigned by reference.
So when you change the 'parameter' variable, you're also changing it's original value.
This does not happen with other parameter types (strings, ints, arrays), but only with objects.
For more details, read References and Objects
Also, I belive there's a typo in your f($e,$g): shouldn't it be $g = 2?
If you need to tamper with the object but still keep a copy it's original value, do a clone:
$b = clone $var;
becase:
$b = $var;
will result in the same thing. $b will still point to $var and all changes on $b will reflect on $var;

PHP array reference

I have this php code
$a = array('one');
$b[0] = &$a[0];
$c = $a;
$b[0] = 'three';
$a = array('two');
var_dump($a); echo '<br/>';
var_dump($b); echo '<br/>';
var_dump($c); echo '<br/>';
Which outputs this ->
array(1) { [0]=> string(3) "two" }
array(1) { [0]=> &string(5) "three" }
array(1) { [0]=> &string(5) "three" }
$b and $a points to the same value but $c should not point to the same value, it should have its own copy of the value. Then when I change the value of $b to three I don't understand why the value of $c also changes. How can I prevent this? Also, when I change the value of $a to 'two', why doesn't $b also changes to 'two'?
Thanks in advance.
Your problem is:
$b[0] = &$a[0];
This references the first element of the array. As evident by the var_dump() output &string(5).
So $b does not reference the entire array, only the first element.
To reference the entire array, you need to do:
$b = &$a;
Doing so works as you expect:
// $a
array(1) {
[0]=>
string(3) "two"
}
// $b
array(1) {
[0]=>
string(3) "two"
}
// $c
array(1) {
[0]=>
string(3) "one"
}
Read more about Arrays in PHP.
This is a step-by-step explanation:
$a = array('one');
$b[0] = &$a[0];
$b is now an array and its first element references the first element of $a. In the symbol table, $b[0] and $a[0] point to the same underlying zval.
$c = $a;
$c references $a now, but a copy has not been made (copy on write semantics). It looks a lot like this:
$c ----/----> $a $b
(0: 'one') 0: 'one' <---- 0: 'one'
The next statement:
$b[0] = 'three';
This updates $a[0] as well, and in turn updates $c[0], because $a itself is not changed. It now looks like;
$c ----/----> $a $b
(0: 'three') 0: 'three' <---- 0: 'three'
Next statement:
$a = array('two');
Disconnect $a from $c and $a[0] from $b[0].
$c $a $b
0: 'three' 0: 'two' 0: 'three'
Prevention
To prevent this behaviour, you need to reference the whole array instead of just one element:
$b = &$a;
By doing this, $b and $a now reference the same zval (the array itself), so any changes made will disconnect it from $c.
I would consider these edge cases of the language though and I would advice not to use references if you don't need to.
It's important to understand what you're doing when assigning by reference vs value. Let's go line by line.
$a = array('one');
Memory location created, we'll call it M1. $a points to M1 and within M1 we have an array with 1 entry, let's call it M1-A1.
$b[0] = &$a[0];
Now what we're doing is pointing $b[0] to M1-A1. Remember that $a[0] also points to M1-A1, so both are pointing to this particular part of memory. Remember that $b itself has it's own memory location, M2, but within M2 we point to M1-A1 (i.e. M2-A1 is pointing to M1-A1).
$c = $a;
Since we're not assigning by reference, what we get is a new memory location, let's call it M3, but within M3 there is an array with the first entry still pointing to M1-A1.
So now we have M1, M2, M3 with an array entry in M2 and M3 pointing to M1-A1.
$b[0] = 'three';
Since $b[0] actually points to M1-A1, we're actually changing the value of M1-A1 memory spot. So anything that references M1-A1's spot will also see this value change.
$a = array('two');
We're completely changing the memory location for $a at this point. Originally it was M1, now we're creating a new memory location, M4. Nothing else points to M4 and M4-A1 DOES NOT point to M1-A1.
So when we do the var dump we get the values you mentioned.
I probably made this more confusing but try drawing it on paper and it'll be pretty clear. Understand that everything is stored in memory and variables just point to memory locations. Once you understand that principle, it'll all fall in place.
They all refer to the same array object in memory

Differences between $var = null and unset($var) [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
What's better at freeing memory with PHP: unset() or $var = null
As far as the garbage collection goes, is 1 better than the other in any circumstances?
EDIT:
Particularly if $var is a very large variable with a lot of levels of recursion and other objects (so to do with recursive cleanup of large objects)
EDIT:
Removed this:
I can think of only 1 thing, and that's that isset($var) will respond differently in either case.
Because apparently I was mistaken... They both react the same.
unset($var);
// You will get Undefined variable Notice.
if ($var) {}
$var = null;
// it's ok.
if ($var) {}
Addition of GC of php.
PHP's Garbage Collection is based on the refcount of the zval, if refcount becomes 0, then the zval can be freed.
So if $a = $b = array(/*a very large array*/);, unset only $a or only $b won't free the memory of the large array.
unset($a); or $a = null or assign another value to $a will all let the refcount decrease by 1, but the memory will be freed only when the refcount decrease to 0.
unset does not force immediate memory freeing but leaves it for the gc. $var = null; however forces immediate memory release.
See example:
// $a = NULL; (better I think)
$a = 5;
$b = & $a;
$a = NULL;
print "b $b "; // b
print(! isset($b)); // 1
?>
It is also worthy to note that in the case of an array unset destroys the variable completely. i.e.:
<?php
$ar = array(1,2,3,4);
var_dump($ar);
echo "<br />";
unset($ar[2]);
var_dump($ar);
echo "<br />";
$ar[1] = null;
var_dump($ar);
?>
Returns the output:
array(4) { [0]=> int(1) [1]=> int(2) [2]=> int(3) [3]=> int(4) }
array(3) { [0]=> int(1) [1]=> int(2) [3]=> int(4) }
array(3) { [0]=> int(1) [1]=> NULL [3]=> int(4) }
$a = 5;
$b = &$a;
unset($b); //just say $b should not pointer to any var
print $a; // 5
$a = 5;
$b = &$a;
$b = null;
print $a; // nothing, because $a = null

Categories