No PHP, as variáveis podem ser de 2 tipos: valores primitivos ou referências. Para especificar que uma variável será passada por referências em uma função, nós usamos o &
na frente do nome da variável. Assim como em muitas linguagens, alterar uma variável passada por referência altera o valor da variável ao qual ela está se referindo. Caso não seja marcada com o & e a variável receba um valor primitivo, a variável representará um valor primitivo e qualquer alteração nessa variável não influencia outras variáveis. Diante disso, vamos analisar um exemplo. Qual a saída do seguinte código?
$list = [0, 0, 0];
foreach ($list as $key => &$field) {
$field = $key + 1;
}
print_r($list);
foreach ($list as $key => $field) {
$field = $field + 3;
}
print_r($list);
Alternativa A
Output:Array
(
[0] => 0
[1] => 0
[2] => 0
)
Array
(
[0] => 0
[1] => 0
[2] => 0
)
Alternativa B
Output:Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 4
[1] => 5
[2] => 6
)
Alternativa C
Output:Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Se você respondeu alternativa A, B ou C, você errou. A resposta correta é a seguinte:
Output:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 8
)
Mas como é possível isso? Da onde surgiu esse 8? Somente depois de alguns bugs em produção que eu descobri o que esse código realmente faz.
Vamos analisar o primeiro foreach.
$list = [0, 0, 0];
foreach ($list as $key => &$field) {
$field = $key + 1;
}
print_r($list);
Esse foreach utiliza a variável $field
como referência. Por isso, na primeira iteração, $field
aponta para o primeiro item do array. Como $key = 0
, temos:
$list = [
1, // $field está apontando para este item
0,
0
];
Na segunda iteração, $key = 1:
$list = [
1,
2, // $field está apontando para este item
0
];
Na terceira iteração, $key = 2:
$list = [
1,
2,
3 // $field está apontando para este item
];
Por isso, quando imprimos o valor de $list pela primeira vez, temos:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Nenhuma novidade. Exatamente como esperado. Como usamos a variável $field
como referência, é natural que o array original seja alterado. Entretanto, vamos para o segundo foreach:
foreach ($list as $key => $field) {
$field = $key + 3;
}
print_r($list);
Qual o valor inicial de $list
?
$list = [
1,
2,
3 // $field está apontando para este item
];
E esse $field
? Aqui, temos uma observação importante: a variável $field
ainda está apontando para o último item da lista. O que acontece na próxima instrução?
foreach ($list as $key => $field)
Quando o foreach é executado, note que estamos atribuíndo o primeiro item da lista para a variável $field
. O que é a variável $field
? O último elemento da lista. O primeiro item da lista é o 1. Por isso, estamos atribuíndo 1 para o último item da lista. Assim, no início da primeira iteração, temos:
$list = [
1,
2,
1 // $field está apontando para este item
];
Após $field = $key + 3;
, temos:
$list = [
1,
2,
4 // $field está apontando para este item
];
No início da segunda iteração, estamos atribuíndo o valor do segundo item para a variável field. Por isso, o valor da lista no início da segunda iteração será:
$list = [
1,
2,
2 // $field está apontando para este item
];
Por isso, no final da segunda iteração, nós temos:
$list = [
1,
2,
5 // $field está apontando para este item
];
No início da terceira iteração, o valor do terceiro item é atribuído para $field
. Como o valor já é 5
, não há mudanças:
$list = [
1,
2,
5 // $field está apontando para este item
];
No final da terceira iteração, é adicionado 3 na variável $field.
$list = [
1,
2,
8 // $field está apontando para este item
];
Por fim, o último print exibe o seguinte resultado:
Array
(
[0] => 1
[1] => 2
[2] => 8
)
Conclusão
Para programadores novos de PHP (ou mesmo para programadores com anos de experiência), esse comportamento é contraintuitivo e pode gerar bugs muito difíceis de rastrear. Logo, é necessário tomar certos cuidados quando for utilizado referências em PHP. Eu sugiro sempre que possível evitar o uso de variáveis de referências. Entretanto, se não for possível, lembre-se de limpar a variável de referência assim que não for mais necessária.
$list = [0, 0, 0];
foreach ($list as $key => &$field) {
$field = $key + 1;
}
unset($field);
print_r($list);
foreach ($list as $key => $field) {
$field = $field + 3;
}
print_r($list);
Como foi feito unset da variável $field
, o resultado intuitivo é obtido:
Output:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 3
)