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).