PHP para Iniciantes: Tipos de Dados Primitivos - Iteráveis

Confesso que acho esse tipo de dado bem estranho. Ele foi introduzido recentemente no PHP, lá pela versão 7.1. E acredito que só foi introduzido como um tipo para nos possibilitar mencionar um array e um object ao mesmo tempo partindo da premissa de que representam um conjunto de estruturas de dados.

Como eu falei no artigo passado, arrays são mapas ordenados e sempre podem ter mais de um elemento. Trazendo a tona o exemplo do depósito da Amazon, se você precisa buscar uma informação por um dado que não é a chave, você precisa olhar cada produto em cada prateleira até encontrar o que precisa.

Essa ação de olhar um a um chama-se iterar. Uma iteração é o ato de pegar algo de uma lista enquanto está pegando um a um. Quer uma analogia diferente?

Quando você está no supermercado fazendo as compras do mês e chega a hora de pegar aquele saco de alimento perecível, você se preocupa com a data de validade e a aparência do produto. Então você começa a olhar um saco por vez, checando se atende aos requisitos para levar pra casa. Quando você encontra o saco perfeito, acabou. Abandona o resto e vai embora continuar as compras.

Falando em linguagem de programação, você iterou sobre a lista de sacos e, em cada iteração, avaliou a qualidade deles. Ao encontrar o primeiro saco bom, pegou ele e parou de iterar. Algo parecido com isso:

<?php

$sacos = [
    ["valido" => false, "aparencia" => "boa"],
    ["valido" => true, "aparencia" => "ruim"],
    ["valido" => true, "aparencia" => "boa"],
];
$sacoEscolhido = null;

foreach ($sacos as $chave => $saco) {
    if ($saco["aparencia"] != "boa") {
        continue;
    }
    if (!$saco["valido"]) {
        continue;
    }
    $sacoEscolhido = $chave;
    break;
}

echo $chave; // 2

Foreach? $chave => $saco? Que que é isso tudo, Kiko?

Calma, não vamos abordar isso tudo ainda. Mas só para deixar um gostinho...

foreach é uma estrutura de iteração similar ao for, cuja tradução literal significa para cada item de $... chame de $... e faça { .... }. No exemplo acima, você pode ler como para cada item de $sacos, chame de $saco e faça tudo que está dentro do escopo.

E a $chave?

Lembra que na construção de arrays nós temos uma sintaxe de atribuição de chave ao valor assim? chave => valor. Da mesma forma, se você quiser capturar, ao mesmo tempo, o item da iteração atual e sua chave, basta usar essa mesma sintaxe. Porém, se eu não quisesse usar isso, bastaria ter feito foreach ($sacos as $saco).

E dentro do escopo eu coloquei dos ifs separados para deixar a legibilidade mais clara. O primeiro checa se a aparência está boa... Se não está, ele manda a iteração continuar, ou seja, devolve o saco para a estante e pega o próximo. O segundo é uma situação similar: se não está na validade, devolve o saco e paga o próximo.

E se não tiver nenhum saco que atenda as condições?

Então nenhum saco vai ser escolhido. Vai passar por todos os sacos e no final vai deixar todos lá mesmo. É isso que é iterar, mas você entendeu o que é um iterável?

Qualquer coisa que dê para iterar?

Praticamente isso, rs. Antes do PHP 7.1, você poderia implementar classes na Programação Orientada a Objeto capazes de realizar iteração em seus dados. Tem muitos exemplos disso nos frameworks que trouxeram lógica de Collections antes disso tudo. O uso de Geradores também é essencial para eles, e é um assunto bem mais pra frente também.

Mas no fim, com esse tipo de dados especificando quando algo é iterável, o PHP consegue abstrair muita coisa. Você já não precisa validar se é um array ou uma das infinitas possibilidades nas hierarquia de classes. Basta checar se é um iterável e pronto.

E se você quer exigir que um tipo seja iterável na assinatura de uma função, basta indicar que aquele parâmetro é iterable.

Ok, ok... Mas como eu crio um iterable?

Criando iteráveis

A forma mais óbvia é escrevendo um array... Acabamos de fazer isso, afinal.

Outra forma mais legal é escrevendo um gerador (yield). Mesmo sem explicar agora eu vou soltar um exemplo abaixo.

Já a forma mais avançada, é fazendo uma classe implementar a interface nativa Traversable combinando com uma das interfaces de iteradores: Iterator, caso queira implementar todo o controle para iterar, ou IteratorAggregate, para somente retornar o iterável em um método só.

<?php

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

function gerador(): iterable
{
    yield 1;
    yield 2;
    yield 3;
    yield 4;
}

class Iteravel implements Traversable, IteratorAggregate
{
    private array $collection = [];

    public function append($item): void
    {
        $this->collection[] = $item;
    }

    public function getIterator(): iterable
    {
        foreach($this->collection as $item) {
            yield $item;
        }
    }
}

$iteravel = new Iteravel();
$iteravel->append(1);
$iteravel->append(2);
$iteravel->append(3);
$iteravel->append(4);



echo "Array" . PHP_EOL;

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

echo "######" . PHP_EOL;

echo "Gerador" . PHP_EOL;

foreach(gerador() as $item) {
    echo $item . PHP_EOL;
}

echo "######" . PHP_EOL;

echo "class Iteravel" . PHP_EOL;
foreach($iteravel as $item) {
    echo $item . PHP_EOL;
}

Tá... Ainda não entendi as vantagens.

Antigamente, para checar se um dado era iterável antes de cometer um possível erro, vários desenvolvedores precisavam constantemente checar se o dado era um array, um objeto que implementava uma classe de iteração, etc. Levando-os a criar funções específicas para identificar esses casos de forma mais genérica.

Agora com o tipo iterable, basta usar uma função is_iterable e pronto: tá garantido. Acredito que vão ter funções nativas que vão usar esse recurso, inclusive, para nos forçar a seguir os padrões de iteráveis no lugar de colocar qualquer informação.

Kiko, strings são consideradas iteráveis?

OOOOPA, excelente questionamento... Mas não. Apesar de ser possível fazer um foreach com uma string para iterar sobre cada caractere, não significa que seja um dado iterável. Afinal, string pode ser a menor informação ou a informação por si só. Então você não pode considerar isso como um iterável por não ter certeza se aquela informação pode ser interpretada de forma tão minuciosa. Por exemplo, se colocarmos as letras do alfabeto em uma string, na nossa cabeça, isso é iterável, pois são letras. Se quisermos enxergar dessa forma, então cada letra do texto é um item. Mas se colocarmos meu apelido "Kiko", você não pode ler isso menor do que "Kiko". Essas quatro letras compõem uma única informação e que não é iterável.

Por esse motivo, string não pode ser considerada iterável mesmo sendo um array de bytes.

Captou? Achou interessante? Comenta aí e vamos ver como que estão os ânimos sobre os artigos, por favor! Se ainda restou alguma dúvida, manifeste-se e tentarei saná-la, fechado?!

Inté!