PHP para Iniciantes: Estruturas de Controle - FOREACH

No artigo anterior (desta série), falei sobre a estrutura for. De quebra, em outro artigo, fiz uma implementação da estrutura de forma recursiva e semântica, mostrando o que rola por baixo das execuções.

Então agora você já sabe como escrever um laço genérico, inclusive dá pra fazer o que vamos escrever hoje com for.

Ué, Kiko... Então pra que serve o foreach?

Para trazer uma melhor semântica para um contexto específico.

O que faz um foreach?

foreach...

For Each significa Para Cada em inglês. Isso evidencia que toda entrada desse tipo de estrutura precisa ser um conjunto de dados. E é isso mesmo, nós já temos um tipo de dados primitivos que identifica esse caso, o tipo iterable.

Com isso, nós podemos executar uma ação para cada elemento desse conjunto, não necessariamente tendo como objetivo afetar os dados desses caras.

E como isso se assemelha a um for, Kiko?

Simples: todo foreach tem como condição percorrer todos os elementos do conjunto. Em um for, isso seria algo como:

<?php

$array = [1, 2, 3, 4, 5, 6];
$quantidadeDeElementos = count($array);

for($indice = 0; $indice < $quantidadeDeElementos; $indice++) {
    $elemento = $array[$indice];
    echo $elemento . PHP_EOL;
}

Esse for percorre a lista do início ao fim, imprimindo na tela um valor por linha. Podemos reescrever isso com foreach:

<?php

$array = [1, 2, 3, 4, 5, 6];

foreach ($array as $elemento) {
    echo $elemento . PHP_EOL;
}

Percebe que não precisei capturar o comprimento do $array, nem recuperar o $elemento a partir de um $indice?

Então a semântica dessa estrutura é: PARA CADA ITEM DO ($array), ATRIBUA SEU VALOR À VARIÁVEL ($elemento) E FAÇA ($acao);

E obviamente a linguagem não seguiu essa semântica à risca... Mas ainda rolou um as ali no meio, que serve de dica pra separar o que é iterável e o que é elemento.

Mas Kiko, é só isso de diferença?

Não. Esse exemplo de lista (array com índices sequenciais) é o mais simples que posso mostrar, mas se criarmos um array dicionário, o primeiro código não vai funcionar.

Veja como complexo é percorrer um dicionário com for:

<?php

$array = [
    'um' => 'Kaique',
    'dois' => 'Neris',
    'tres' => 'SwooLeo',
];

$quantidadeDeElementos = count($array);
$indices = array_keys($array); // ['um', 'dois', 'tres']

for($indiceInterno = 0; $indiceInterno < $quantidadeDeElementos; $indiceInterno++) {
    $indice = $indices[$indiceInterno];
    $elemento = $array[$indice];
    echo $elemento . PHP_EOL;
}

QUE

QUE

Exatamente. Em caso de dicionários, você não tem como advinhar como os índices foram escritos, logo, é preciso capturar os índices em forma de lista usando a função array_keys (que extrai as chaves do array).

Uma forma alternativa seria extrair os valores usando array_values:

<?php

$array = [
    'um' => 'Kaique',
    'dois' => 'Neris',
    'tres' => 'SwooLeo',
];

$quantidadeDeElementos = count($array);
$valores = array_values($array);

for($indice = 0; $indice < $quantidadeDeElementos; $indice++) {
    $elemento = $valores[$indice];
    echo $elemento . PHP_EOL;
}

Mais simples? Talvez. Mas nada supera um foreach que funciona para ambos os casos (que é aquele que coloquei lá em cima). É bem mais legível.

Ok, Kiko... Mas e se eu quiser acessar o índice de cada elemento?

Nesse caso, o PHP usa a sintaxe similar ao registro de um dado no array. Você sabe que há duas formas de inicializar um array com valor:

// Em lista
[1, 2, 3, 4]

// Em dicionário
[
    'chave' => 'valor',
]

Nesse caso, o segredo está na sintaxe de dicionário. Basta inserir essa mesma sintaxe no fluxo, mas em formas de variáveis (que vão receber as informações). Por exemplo:

<?php

$array = [
    'um' => 'Kaique',
    'dois' => 'Neris',
    'tres' => 'SwooLeo',
];

foreach($array as $chave => $valor) {
    echo "$chave = $valor" . PHP_EOL;
}

Ou seja:

  • você não precisa separar as chaves dos valores (nem vice-versa);
  • você não precisa capturar o comprimento do iterable;
  • você não se limita a lidar com array, mas qualquer iterable de fato;
  • você consegue compreender o laço baseado na semântica, sem depender da interpretação de condições.
Oh yeaaah

Só benefícios, né? E assim como o for, o foreach também tem a sintaxe procedural:

<?php

foreach($array as $valor):
    // alguma coisa
endforeach;

Lidando com referências

Apesar dos pesares, foreach tem um risco bem importante de mencionar. Se você escrever os exemplos acima, não vai rolar nada de demais nas execuções. Mas se, por ventura, você tentar modificar o valor do elemento, essa alteração não vai refletir no array. Isso porque os dados não são referências às posições no iterable e, por isso, não reflete no valor final.

Como resolvemos isso? Com referências:

<?php

$array = [1, 2, 3, 4];

foreach($array as &$valor):
    $valor *= 2;
endforeach;

var_dump($array); // [2, 4, 6, 8]

Esse foreach está multiplicando todos os valores por 2. Ele faz o que promete.

Então... Onde está a pegadinha, Kiko?

No último loop. Lembra que no for, toda variável inicializada continua existindo fora do laço? O mesmo vale para as variáveis que recebem os valores de chaves e/ou elementos de cada iteração. O último laço irá externalizar a variável $valor como uma referência do último elemento do iterable.

Isso é perigossíssimo, principalmente se você tentar criar uma nova variável com a mesma nomenclatura depois. Você já sabe que o PHP não tem uma sintaxe específica para criar uma nova variável. É simplesmente definir um valor e pronto... Se a variável existia, você sobreescreve o valor, certo? Mas o que acontece quando você define um valor para uma referência?

<?php

$array = [1, 2, 3, 4];

foreach($array as &$valor) {
    $valor *= 2;
    echo $valor . PHP_EOL;
}

var_dump($array); // [2, 4, 6, 8]
$outroArray = ['Kaique', 'Neris', 'SwooLeo'];

foreach($outroArray as $valor) {
    echo $valor . PHP_EOL;
}

var_dump($array); // [2, 4, 6, 'SwooLeo']

Percebeu o risco?!

... E tem como contornar isso, Kiko?

Felizmente sim. O que é necessário fazer, se você realmente precisa usar uma referência, é apagar a referência fora do loop com a função unset. Simples assim:

<?php

$array = [1, 2, 3, 4];

foreach($array as &$valor) {
    $valor *= 2;
}

unset($valor); // retira a referência da variável $valor

var_dump($array); // [2, 4, 6, 8]
$outroArray = ['Kaique', 'Neris', 'SwooLeo'];

foreach($outroArray as $valor) {
    echo $valor . PHP_EOL;
}

var_dump($array); // [2, 4, 6, 8]

Esse é apenas um ponto de atenção. Se você não usar referências, você não precisa fazer isso, fechado?

E por hoje é só! Curtiu? Comenta e compartilha! No próximo artigo irei falar sobre uma estrutura super simples que serve para quebrar laços. Eu mencionei no artigo do-while, então vou deixar o suspense aqui. Logo, logo lanço o artigo aqui, fechado?!

Inté!!