Foreach com List em PHP

Artigo que apresenta o funcionamento do foreach com utilização do comando list no PHP 5.5.

Introdução

Outra melhoria introduzida no PHP 5.5 foi o suporte ao comando list dentro de estruturas foreach. Neste artigo veremos como utilizar este novo suporte, mas antes, veremos rapidamente o que é o list e onde usá-lo.

Sobre o list

List é um elemento nativo da linguagem PHP e permite realizar atribuições de valores de um array para um conjunto de variáveis de forma simplificada. Mas, atenção: list não é uma função.

O funcionamento do list é o seguinte: é montada uma atribuição (utilizando o operador de atribuição, que é representado pelo símbolo "=") sendo que, na esquerda, fica uma lista de variáveis e, na direita, o array de onde serão obtidos os valores para seram atribuidos às variáveis. Os valores são pegos do array a partir da posição 0 (zero) e seguindo os valores inteiros positivos de forma incremental. Veja um exemplo:

$array = array(4, 7, 'a' => 8, 3, 9, 2);

list($a, $b, $c) = $array;

No exemplo acima, as variáveis $a, $b e $c receberam os valores 4, 7 e 3, respectivamente. Note que a posição "a" foi ignorada, pois não é uma posição numérica. Além disso, as posições 4 e 5 foram ignoradas, pois a lista continha apenas 3 variáveis.

O código acima é "convertido" da seguinte forma:

$array = array(4, 7, 'a' => 8, 3, 9, 2);

$c = $array[2];
$b = $array[1];
$a = $array[0];

Portanto, o elemento list é útil quando temos um array que já conhecemos a sua estrutura e queremos manipulá-las de forma mais legível, atribuindo seus valores a variáveis com nomes sugestivos. Note, ainda, que os valores são atribuídos às variáveis da direita para a esquerda, logo, é preciso cuidado se utilizar, por exemplo $x[] dentro de um list.

Observação: caso quisessemos pegar apenas a posição 0 e 2, poderiamos omitir a variável $b do exemplo:

$array = array(4, 7, 'a' => 8, 3, 9, 2);

list($a, , $c) = $array;

Embora a sintaxe fique um pouco "deselegante", é a forma correta de se ignorar uma posição do array. Nem pense em colocar "null" naquela posição, pois o elemento list aceita apenas variáveis ou atributos acessíveis de objetos. Uma forma de deixar o código mais elegante, seria colocar um comentário de bloco na posição vazia, com o conteúdo /* ignora */ ou /* void */.

Utilização prática do list

Uma das utilizações práticas do list é para coletar múltiplos retornos de uma única função. Quando uma função precisa retornar mais de um valor, podemos fazer de duas formas: (i) retornando apenas o valor principal pela função e retornando os demais valores através de parâmetros passados por referência, ou (ii) retornando um array de tamanho fixo, em que cada posição do array representa um resultado.

Na segunda abordagem, a função pode optar por indexar o array com nomes (strings) ou simplesmente indexar o array numericamente. Caso o array seja indexado numericamente, podemos coletar os vários retornos da função através de um list. Veja o exemplo:

/**
 * Funcao que divide um numero por outro e retorna o quociente e o resto
 * @param int $dividendo
 * @param int $divisor
 * @return array
 */
function dividir($dividendo, $divisor) {
    $quociente = floor($dividendo / $divisor);
    $resto = $dividendo % $divisor;

    return array($quociente, $resto);
}

list($quociente, $resto) = dividir(5, 2);

No exemplo, o $quociente recebeu o valor 2, enquanto o $resto recebeu o valor 1.

Embora em boa parte dos casos seja melhor indexar o array com alguma string, em alguns casos pode ser melhor manter a indexação numérica, que é mais rápida e requer menos memória.

Outra utilização comum é a utilização do list em loops que percorrem registros. Por exemplo, ao realizar uma consulta a um banco de dados, podemos percorrer os registros retornados da seguinte forma:

...
$stmt = $pdo->query('SELECT id, tipo, cor, tamanho FROM roupas');
while (list($id, $tipo, $cor, $tamanho) = $stmt->fetch(PDO::FETCH_NUM)) {
    ...
}

Suporte do list em estruturas foreach

No PHP 5.5, foi introduzido o suporte do list em estruturas foreach. Isso significa que, ao percorrer os elementos em um foreach, podemos automaticamente atribuí-los a variáveis pré-definidas. A sintaxe é a seguinte:

...
$stmt = $pdo->query('SELECT id, tipo, cor, tamanho FROM roupas');
foreach ($stmt->fetch(PDO::FETCH_NUM) as list($id, $tipo, $cor, $tamanho)) {
    ...
}

Opcionalmente, é possível obter o índice do elemento percorrido no foreach:

...
$stmt = $pdo->query('SELECT id, tipo, cor, tamanho FROM roupas');
foreach ($stmt->fetch(PDO::FETCH_NUM) as $i => list($id, $tipo, $cor, $tamanho)) {
    ...
}

Os benefícios desta melhoria são: (i) eliminação da necessidade de uma variável para guardar o array obtido na iteração, poupando memória, (ii) redução do tamanho do código-fonte necessário para realizar a operação, e (iii) potencial ganho de legibilidade do código-fonte.

Em termos de funcionalidade, a diferença do foreach para um while é que o foreach reinicia o iterador automaticamente antes de iniciar a iteração (veja método Iterator::rewind).

5 comentários

Anônimo disse...

Muito bem explicado, mas não enxerguei as vantagens apontadas na recuperação de registros de Banco de Dados. Ninguém usa, a não ser em casos muito específicos, $stmt->fetch(PDO::FETCH_NUM). Em 99% das vezes, usamos PDO::FETCH_ASSOC ou PDO::FETCH_OBJECT, muito mais prático e legível. Redução do código-fonte? foreach ($stmt as $row){}, ou while ($row = $stmt->fetch(PDO::FETCH_ASSOC)){} teriam o mesmo efeito. Redução de memória? O que pesa mais, um array com cinco posições ou cinco variáveis? Não estou desconstruindo o seu artigo, apenas fazendo algumas considerações. No mais, muito didático e bem escrito, parabéns!!!

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Anônimo.

Concordo plenamente com as suas considerações. Eu também não uso, nunca usei e não pretendo usar FETCH_NUM. O exemplo foi apenas para mostrar o funcionamento e para os programadores saberem avaliar quando ele pode ou não ser útil e quando o ganho será significativo ou não.

Particularmente, acho este recurso bastante inútil, e creio que nunca utilizarei. Mas também acho que é bom saber que ele existe para que, se alguém usar em algum código, ao menos eu saiba o que ocorre.