Q:

Comportamiento inesperado al modificar valores de un array

Acabo de descubrir que se pueden cambiar los valores de un array directamente dentro de un foreach, si se pasan por referencia, pero he observado un efecto indeseado, y no se porque ocurre. Estoy haciendo las pruebas con PHP 7.3.5 en una "caja de arena online".
Hasta donde yo sé, al recorrer un array, de forma "normal", no se pueden modificar sus valores. Así por ejemplo:
http://sandbox.onlinephpfunctions.com/code/d1befcf1d8b76ab8ac9d5032b1de1244c8854eab
<?php
$origin = [2, 4, 2, 6];
foreach ($origin as $value) {
    $value = 1;
}
print_r($origin);

$origin = ['x', 4, 'x', 6];
foreach ($origin as $value) {
    $value = 1;
}
print_r($origin);

Da como resultado:
Array
(
    [0] => 2
    [1] => 4
    [2] => 2
    [3] => 6
)
Array
(
    [0] => x
    [1] => 4
    [2] => x
    [3] => 6
)

Hasta aquí todo funciona correctamente.

Ahora vamos a modificar el array original. Voy a cambiar los números 2
por strings que guarden una 'x'.
Para esto habitualmente crearía un nuevo array (es decir, no modifico realmente el original, obtengo uno nuevo con lo que necesito):
http://sandbox.onlinephpfunctions.com/code/adbebbee50529652d960a9b20dd80fe7ccda622b
<?php
$origin = [2, 4, 2, 6];

$result = [];
foreach ($origin as $value) {

    // Cuando el valor es 2 se cambia por una 'x'
    if ($value == 2) {
        $result[] = 'x';
    } else {
        $result[] = $value;
    }
}
print_r($result);

o bien:
<?php
$origin = [2, 4, 2, 6];

$result = [];
foreach ($origin as $value) {

    // Cuando el valor es 2 se cambia por una 'x'
    if ($value == 2) {
        $result[] = 'x';
        continue;
    }

    $result[] = $value;
}
print_r($result);

El resultado, de ambos scripts será:
Array
(
    [0] => x
    [1] => 4
    [2] => x
    [3] => 6
)

Ahora voy a obtener de nuevo el array original, volviendo a cambiar las 'x'
por números 2:
<?php
$origin = [2, 4, 2, 6];

$result = [];
foreach ($origin as $value) {

    // Cuando el valor es 2 se cambia por una 'x'
    if ($value == 2) {
        $result[] = 'x';
        continue;
    }

    $result[] = $value;
}
print_r($result);

$origin = [];
foreach ($result as $value) {

    // Cuando el valor es 2 se cambia por una 'x'
    if ($value == 'x') {
        $origin[] = 2;
        continue;
    }

    $origin[] = $value;
}
print_r($origin);

El resultado, una vez más, es predecible:
Array
(
    [0] => x
    [1] => 4
    [2] => x
    [3] => 6
)
Array
(
    [0] => 2
    [1] => 4
    [2] => 2
    [3] => 6
)

Ahora, voy a modificar el array original directamente mientras se recorre:
http://sandbox.onlinephpfunctions.com/code/9f9d78056d94e84d9f675ee0f93355f0b15c8341
<?php
$origin = [2, 4, 2, 6];

// Reasignar algunos valores pasandolos por referencia
foreach ($origin as &$value) {
    if ($value == 2)
        $value = 'x';
}
print_r($origin);

// Reasignar algunos valores pasandolos por referencia
foreach ($origin as &$value) {

    if ($value == 'x')
        $value = 1;
}
print_r($origin);

// Trato de reasignar todos los valores sin pasarlos por referencia
foreach ($origin as $value) {

    $value = 5;
}
print_r($origin);

Y el resultado:
Array
(
    [0] => x
    [1] => 4
    [2] => x
    [3] => 6
)
Array
(
    [0] => 1
    [1] => 4
    [2] => 1
    [3] => 6
)
Array
(
    [0] => 1
    [1] => 4
    [2] => 1
    [3] => 5
)

Problema:
No solo hemos modificado los valores del array original. Al final, hemos podido reasignar directamente el valor de la último posición del array. ¿Por qué ocurre esto?

A:

Este comportamiento viene documentado en el manual véase foreach
Nota del manual:

Advertencia
La referencia del $valor y el último elemento del array permanecen aún después del bucle foreach. Se recomienda destruirlos con unset(). De lo contrario, se experimentará el siguiente funcionamiento:

<?php
$array = array(1, 2, 3, 4);
foreach ($array as &$valor) {
    $valor = $valor * 2;
}
// $array ahora es array(2, 4, 6, 8)

// sin unset($valor), $valor aún es una referencia al último elemento: $array[3]

foreach ($array as $clave => $valor) {
    // $array[3] se actualizará con cada valor de $array...
    echo "{$clave} => {$valor} ";
    print_r($array);
}
// ...hasta que finalmente el penúltimo valor se copia al último valor

// salida:
// 0 => 2 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 2 )
// 1 => 4 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 4 )
// 2 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 )
// 3 => 6 Array ( [0] => 2, [1] => 4, [2] => 6, [3] => 6 )
?>

Básicamente lo que sucede es que la referencia persiste después del bucle mientras esta no sea destruida. En uno de los ejemplos del manual nos indica claramente como romper la referencia mediante el uso de unset():
<?php
$array = array(1, 2, 3, 4);
foreach ($array as &$valor) {
    $valor = $valor * 2;
}
// $array ahora es array(2, 4, 6, 8)
unset($valor); // rompe la referencia con el último elemento
?>

