PHP - foreach lose reference with null coalescing operator - php

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.

Related

PHP Array reference replacement

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

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.

How exactly php spaceship operator compare strings, arrays and objects

I am wondering how the php spaceship operator compares strings, objects and arrays. For example, the below code.
echo "Its Me at SO" <=> "Its Me at SO";
will return 0, as i know all characters are same, count is same. But if i have a code like below:
echo "Its me at SO" <=> "its Me at so";
It will return 1, means that left side is greater than right side, but how? Is it comparing the ASCII values?
Now lets come to arrays. The below code will return 0, as both arrays are equal by count, values and values at each index.
echo [1,2,3] <=> [1,2,3];
But the below code returns -1
echo [1,2,3] <=> [3,2,1];
And i dont understand why? How this operator compares the arrays and how it calculates that the array on left is smaller than the array on right?
And the same goes for the objects.
Can anybody give a detailed answer that how it works with strings, arrays and objects?
Thank you
"Comparisons are performed according to PHP's usual type comparison rules (http://php.net/manual/en/types.comparisons.php)".
1) Yes, it uses the ASCII values
2) If the arrays are different lengths, the Array with fewer values is smaller.
Otherwise it compares the arrays key by key, giving "earlier" values priority. For example comparing $arr1[0] to $arr2[0] first. If $arr1 has a key that doesn't exist in $arr2, the arrays aren't comparable (eg if we're using non-numeric arrays).
// Arrays are compared like this with standard comparison operators
// $arr1 and $arr2 are arrays
function standard_array_compare($arr1, $arr2)
{
// If either array has more values, that array is considered "larger"
if (count($arr1) < count($arr2)) {
return -1; // $arr1 < $arr2
} elseif (count($arr1) > count($arr2)) {
return 1; // $arr1 > $arr2
}
//Otherwise compare the array values directly
foreach ($arr1 as $key => $val) {
if (!array_key_exists($key, $arr2)) {
return null; // uncomparable, these arrays do not have the same keys
} elseif ($val < $arr2[$key]) {
return -1; // $arr1 < $arr2
} elseif ($val > $arr2[$key]) {
return 1; // $arr1 > $arr2
}
}
return 0; // $arr1 == $arr2
}
Note, the above is not PHP's actual code, just an approximate representation of the logic used.
Essentially, then, it treats an array in a similar way to comparing a big-endian number. It compares $arr1[0] to $arr2[0]. If they are the different it returns -1 or 1 depending which is larger. If they are the same it moves on to $arr1[1] and $arr2[1]. If all values are the same it returns 0 (arrays are equal)
While not exactly the same, it might be simpler to consider [1,2,3] <=> [3,2,1] as basically equivalent to 123 <=> 321...
According to the new features documentation
Comparisons are performed according to PHP's usual type comparison rules.
<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Spaceship Arrays php8
<?php
echo [] <=> []; // result is 0
echo [1, 2, 3] <=> [1, 2, 3]; // result is 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1
echo [1,2,3,1] <=> [1,2,4];//1 //thats mean index first piority,1st array index number getter then 2nd array so its positive answer 1
echo [1,2,4] <=> [1,2,3,1]//-1

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

What does the PHP operator =& mean? [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
What do the "=&" and "&=" operators in PHP mean?
I found the operator "=&" in the following code, and I do not know what it means. What does it mean and what does it do?
The code where I read it:
function ContentParseRoute($segments)
{
$vars = array();
//Get the active menu item
$menu =& JSite::getMenu();
$item =& $menu->getActive();
// Count route segments
$count = count($segments);
....
This isn't an assignment (=) by reference (&).
If you were to say:
$a = 42;
$b =& $a;
You are actually saying assign $a by reference to $b.
What assigning by reference does is "tie" the two variables together. Now, if you were to modify $a later on, $b would change with it.
For example:
$a = 42;
$b =& $a;
//later
echo $a; // 42
echo $b; // 42
$a = 13;
echo $a; // 13
echo $b; // 13
EDIT:
As Artefacto points out in the comments, $a =& $b is not the same as $a = (&$b).
This is because while the & operator means make a reference out of something, the = operator does assign-by-value, so the expression $a = (&$b) means make a temporary reference to $b, then assign the value of that temporary to $a, which is not assign-by-reference.
It is the referential assignment operator.
This means that when you modify the LHS of the operator later on in code, it will modify the RHS. You are pointing the LHS to the same block of memory that the RHS occupies.
Here's an example of it in use:
$array = array('apple', 'orange', 'banana');
// Without &
foreach($array as $d)
{
$d = 'fruit';
}
echo implode(', ', $array); // apple, orange, banana
// With &
foreach($array as &$d)
{
$d = 'fruit';
}
echo implode(', ', $array); // fruit, fruit, fruit
Not an explanation, but an example of being able to use the & operator without using it in an =& assignment.

Categories