Assign by reference PHP misunderstaning - php

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.

Related

PHP, assigning a value to a variable by reference

a friend asked me to analyze the output of the following simple lines.
I defined a variable x that has the string PHP, and a table called a which is a reference to x, so any changes to one of them will affect the other. First, I assigned the value "Mysql" to the first element of a, the value of x changed too.
But in the second affectation, when the second element of the table a got the value "test", the value of the variable x didn't change, any explanation?
<?php
$x = "PHP";
$a[] = &$x;
$a[0] = "MySql";
echo $a[0]; // Output: MySql
echo $x; // Output: MySql
$a[1] = "test";
echo $a[1]; // Output: test
echo $x; // Output: MySql
// why last output is mySql and not test?
?>
The reference to $x is never in $a[1]. It is only in $a[0] and therefore won't change when you assign 'test' to $a[1].
$a[] = &$x; is just adding &$x to the end of the array $a. $a at that point in your code is empty, so you are assigning &$x to the first index of $a ($a[0])

Smarty assignByRef inside loop

I need to render some Smarty template for different values inside a loop (a PHP loop, not a Smarty foreach), in the following way (just an example):
$a = 0;
$b = 0;
$output = "";
$tmpl = "$a, $b";
$smarty->assignByRef('a', $a['a']);
$smarty->assignByRef('b', $b['b']);
for (int i = 0; i < 10; ++i) {
++$a;
++$b;
$output .= $smarty->fetch("string:" . $tmpl);
}
My doubt is about assignByRef. The Smarty v3 docs says:
With the introduction of PHP5, assignByRef() is not necessary for most
intents and purposes. assignByRef() is useful if you want a PHP array
index value to be affected by its reassignment from a template.
Assigned object properties behave this way by default.
but I don't fully understand what does that technical note means. So, can I use assignByRef that way or not? or using just assign will produce the same output?
PHP 4 objects were passed by value, unless the user explicitly specified the reference by prepending ampersand: &$variable. For this reason, function arguments that were likely to consume a big amount of memory were passed by reference in order to optimize memory usage:
function f(&$huge) {
// ...
}
PHP 5 variables are passed by reference, even if the user did't specify it explicitly (the ampersand character is not used). By assigning one variable to another we only create a new container (internally called zval) for the same data in memory. Consider this:
$a = new stdClass;
$b = $a;
The first line allocates memory for variable $a and an object of stdClass, and stores the object's identifier into the variable. The second line allocates memory for variable $b, stores the object's identifier into the $b variable, and increments an internal reference counter. The reference counter value shows how many times the object is referenced in the code. When $b variable is destroyed, the reference counter is decremented by one. When the value of reference counter becomes equal to zero, the object's memory is deallocated. The following code demonstrates the idea:
$a = new stdClass;
debug_zval_dump($a);
$b = $a;
debug_zval_dump($a);
$c = $a;
debug_zval_dump($a);
$c = null; // destroy $c
debug_zval_dump($a);
$b = null; // destroy $b
debug_zval_dump($a);
Output
object(stdClass)#1 (0) refcount(2){
}
object(stdClass)#1 (0) refcount(3){
}
object(stdClass)#1 (0) refcount(4){
}
object(stdClass)#1 (0) refcount(3){
}
object(stdClass)#1 (0) refcount(2){
}
But when a variable is modified, PHP versions 5 and 7 create a copy of the variable in order to keep the original value (variable) intact.
$m1 = memory_get_usage();
$a = str_repeat('a', 1 << 24);
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 16,781,408
$b = $a;
$c = $a;
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 16,781,472
$b[0] = 'x';
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 33,562,880
$c[0] = 'x';
echo number_format(memory_get_usage() - $m1), PHP_EOL;
// 50,344,288
The same is applied to the context of the function arguments. Thus, if a variable is supposed to be used for read only, there is no need for passing it by reference explicitly. The words in the Smarty documentation mean that in most cases, you pass variables to the templates, and usually do not expect the template to change them. You need to pass a variable by reference only when you really want the variable to be modified in the template. The same concept is applied to any function arguments in PHP 5 and newer.

Changing a value in a copy of an array changes the value in the original arra

I have some issues with variables in php that i don't understand.
This is a simplified code example of the issue.
//Create an initial array with an sub-array
$a = array();
$a['test'] = array(1,2);
//Create an reference to $a['test'] in $b
//Changing $b[0] should now change the value in $a['test'][0]
$b = &$a['test'];
$b[0] = 3;
//Create an copy of $a into $c
$c = $a;
//Change one value in $c, which is an copy of $a.
//This should NOT change the original value of $a as it is a copy.
$c['test'][1] = 5;
print_r($a);
print_r($b);
print_r($c);
This is the output:
Array
(
[test] => Array
(
[0] => 3
[1] => 5
)
)
Array
(
[0] => 3
[1] => 5
)
Array
(
[test] => Array
(
[0] => 3
[1] => 5
)
)
The script creates an array with an sub-array and puts two values in it.
A reference to the sub-array is then put into b and one of the values in a is changed in this way.
I then make a copy of a into c.
I then change one value of c.
As c is a copy of a i would expect that the change on c did not affect a. But the output tells a different tale.
Can anyone explain why changing a value in the variable $c affect the value in $a when $c is just a copy of $a? Why is there a 5 in the values of $a?
You're assigning $b to $a by reference (that's what the & prefix does). Any changes to $b will effectively modify $a. Just force a declaration assignment:
$b = $a['test'];
$c does not modify $a. Here's the order of what's going on, and why the arrays are identical:
$a['test'] is assigned an array of 1,2.
$b is assigned as a reference to $a['test'], and modifies its values
$c is then assigned to $a, which has now been modified by $b.
I think i found the answer to my own question... On this page: http://www.php.net/manual/en/language.references.whatdo.php
I can't really understand why it does what it does. I do understand that i should probably avoid mixing references and arrays in the future.
Im refering to this section:
Note, however, that references inside arrays are potentially
dangerous. Doing a normal (not by reference) assignment with a
reference on the right side does not turn the left side into a
reference, but references inside arrays are preserved in these normal
assignments. This also applies to function calls where the array is
passed by value. Example:
<?php
/* Assignment of scalar variables */
$a = 1;
$b =& $a;
$c = $b;
$c = 7; //$c is not a reference; no change to $a or $b
/* Assignment of array variables */
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* The contents of $arr are changed even though it's not a reference! */
?>
In other words, the reference behavior of arrays is defined in an
element-by-element basis; the reference behavior of individual
elements is dissociated from the reference status of the array
container.
You are passing the reference $a to $b by using $b = &$a['test']; Hence
change
$b = &$a['test'];
to
$b = $a['test'];

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

Arrays and references in PHP

Today I was reading article What Reference Do from PHP's official manual page and found the following piece of code:
<?php
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
And that what manual says about this code:
References inside arrays are potentially dangerous. Doing a normal
(not by reference) assignment with a reference on the right side does
not turn the left side into a reference, but references inside arrays
are preserved in these normal assignments. This also applies to
function calls where the array is passed by value.
Can someone explain me why after execution of the code we will have $a and $arr equal to 2 ?
Perhaps this will make it clearer:
$arr = array(1,1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
$arr2[1]++;
/* $a == 2, $arr == array(2) */
var_dump($arr);
//outputs array(2) { [0]=> &int(2) [1]=> int(1) }
The reason the value is incremented is because the reference inside the array is preserved in the normal assignment. $arr2[0], $arr[0] and $a now all refer to the same value, even though $arr2 is a copy of $arr. Note how $arr2[1]++ does not increment $arr[1].
Do a var_dump($arr); and you'll see the issue. $arr[0] will have an &int type.
This means $arr[0] becomes a reference to a value.
And the $arr array will actually have a reference as its first value.
When you duplicate the array, the reference is carried over as the first element will keep being a reference and will still modify the referenced value.
It's an array thing. Seems really weird but once you understand it, it makes sense... as much as PHP makes sense :)
PS: This behavior is why call_user_func_array() can take an array() of references as it's argument and allows you to call functions that accept reference arguments.

Categories