Iterators e SPL
LidoEsta aula explora o uso de iteradores no PHP, focando nas interfaces Iterator e IteratorAggregate, além de classes utilitárias da SPL como ArrayObject e SplStack. Você aprenderá a criar iteradores personalizados para controlar a iteração sobre coleções de dados, entenderá quando cada abordagem é mais adequada e praticará com exercícios.
Nesta aula, você vai aprender a dominar a iteração de coleções em PHP usando as interfaces Iterator e IteratorAggregate, além de explorar algumas classes da Standard PHP Library (SPL) que facilitam o trabalho com estruturas de dados. Entender esses conceitos é essencial para escrever código mais expressivo, eficiente e orientado a objetos, especialmente quando você precisa percorrer coleções de dados de forma personalizada.
Vamos começar pela interface Iterator, que permite que você defina exatamente como a iteração deve funcionar para uma determinada classe. Em seguida, veremos uma alternativa mais concisa com IteratorAggregate, que delega a lógica de iteração para um iterador externo. Depois, exploraremos algumas classes SPL como ArrayObject e SplStack, que já vêm prontas para uso e implementam essas interfaces. Por fim, discutiremos quando cada abordagem é mais adequada, para que você possa tomar decisões conscientes em seus projetos.
Interface Iterator
A interface Iterator é uma das interfaces centrais da SPL. Ela define cinco métodos que qualquer classe deve implementar para se tornar iterável: current(), key(), next(), rewind() e valid(). Esses métodos são chamados pelo construtor de foreach em PHP, permitindo que você controle exatamente como os elementos da sua coleção são percorridos.
Vamos entender cada método:
- current(): retorna o elemento atual da iteração.
- key(): retorna a chave (índice) do elemento atual.
- next(): avança o ponteiro interno para o próximo elemento.
- rewind(): reinicia o ponteiro para o primeiro elemento.
- valid(): retorna
truese a posição atual é válida (ou seja, existe um elemento na posição atual).
Para ilustrar, vamos criar uma classe que representa uma lista de números e implementamos Iterator para percorrer apenas os números pares. Isso mostra como podemos filtrar elementos durante a iteração, algo que um foreach simples não faria sem uma estrutura condicional.
<?php
class EvenNumbers implements Iterator {
private array $numbers;
private int $position = 0;
public function __construct(array $numbers) {
$this->numbers = array_values($numbers); // reindexa
}
public function current(): int {
return $this->numbers[$this->position];
}
public function key(): int {
return $this->position;
}
public function next(): void {
$this->position++;
// Avança até o próximo número par
while ($this->position < count($this->numbers) && $this->numbers[$this->position] % 2 !== 0) {
$this->position++;
}
}
public function rewind(): void {
$this->position = 0;
// Se o primeiro não for par, avança
if ($this->valid() && $this->numbers[$this->position] % 2 !== 0) {
$this->next();
}
}
public function valid(): bool {
return isset($this->numbers[$this->position]);
}
}
$numbers = [1, 2, 3, 4, 5, 6, 7, 8];
$even = new EvenNumbers($numbers);
foreach ($even as $key => $value) {
echo "$key => $value\n";
}
// Saída:
// 1 => 2
// 3 => 4
// 5 => 6
// 7 => 8
Observe que a implementação é um pouco complexa porque precisamos controlar a posição manualmente. Felizmente, o PHP oferece uma alternativa mais simples com IteratorAggregate.
IteratorAggregate
A interface IteratorAggregate permite que você implemente apenas um método: getIterator(), que deve retornar um objeto que implemente Iterator ou Traversable. Isso simplifica muito a criação de classes iteráveis, pois você pode usar um ArrayIterator ou um Generator para delegar a lógica de iteração.
Vamos reescrever o exemplo anterior usando IteratorAggregate e um Generator. Os generators são uma forma elegante de criar iteradores com sintaxe simples usando yield.
<?php
class EvenNumbers implements IteratorAggregate {
private array $numbers;
public function __construct(array $numbers) {
$this->numbers = array_values($numbers);
}
public function getIterator(): Traversable {
foreach ($this->numbers as $number) {
if ($number % 2 === 0) {
yield $number;
}
}
}
}
$numbers = [1, 2, 3, 4, 5, 6, 7, 8];
$even = new EvenNumbers($numbers);
foreach ($even as $value) {
echo "$value\n";
}
// Saída:
// 2
// 4
// 6
// 8
Muito mais limpo! O generator cuida de toda a lógica de iteração, e você só precisa se preocupar com a lógica de negócio. Além disso, o método getIterator() pode retornar qualquer iterador, como um ArrayIterator se você quiser percorrer todos os elementos.
SPL (ArrayObject, SplStack)
A SPL (Standard PHP Library) oferece várias classes prontas que implementam IteratorAggregate ou Iterator. Duas delas são muito úteis no dia a dia: ArrayObject e SplStack.
ArrayObject
O ArrayObject permite que você trabalhe com arrays como objetos, mantendo a sintaxe de array (acesso via colchetes) e adicionando métodos úteis. Ele implementa IteratorAggregate, então você pode usá-lo diretamente em foreach. Além disso, ele oferece métodos como count(), append(), asort(), entre outros.
<?php
$obj = new ArrayObject(['apple', 'banana', 'cherry']);
// Acessa como array
$obj[] = 'date';
echo $obj[1]; // banana
// Itera normalmente
foreach ($obj as $key => $value) {
echo "$key: $value\n";
}
// Usa métodos de array
echo count($obj); // 4
$obj->asort();
print_r($obj->getArrayCopy());
O ArrayObject é especialmente útil quando você precisa passar um array por referência ou aplicar comportamentos de objeto a dados que normalmente seriam arrays.
SplStack
SplStack é uma implementação de pilha (stack) que segue a política LIFO (Last In, First Out). Ela implementa Iterator, permitindo iterar sobre os elementos na ordem da pilha (do topo para a base). Métodos principais: push(), pop(), top(), isEmpty().
<?php
$stack = new SplStack();
$stack->push('primeiro');
$stack->push('segundo');
$stack->push('terceiro');
echo $stack->top(); // terceiro
// Itera do topo para a base
foreach ($stack as $item) {
echo $item . PHP_EOL;
}
// terceiro
// segundo
// primeiro
$stack->pop(); // remove 'terceiro'
echo $stack->top(); // segundo
Outras classes SPL de estruturas de dados incluem SplQueue (fila), SplHeap, SplFixedArray, entre outras. Cada uma tem suas particularidades e é importante conhecê-las para escolher a estrutura certa para o problema.
Quando usar
A escolha entre Iterator e IteratorAggregate depende do seu cenário. Se você precisa de um controle muito fino sobre a iteração, como pular elementos ou manter um estado interno complexo, a implementação direta de Iterator pode ser necessária. Porém, na maioria dos casos, IteratorAggregate é mais simples e menos propenso a erros, especialmente se você puder usar um Generator.
Quanto às classes SPL, use ArrayObject quando quiser tratar um array como objeto e aproveitar métodos utilitários. Use SplStack quando precisar de uma pilha LIFO, SplQueue para fila FIFO, e assim por diante. Essas estruturas são mais eficientes que arrays nativos em operações de inserção/remoção nas extremidades, pois evitam a reindexação.
Em geral, se você está criando uma classe que representa uma coleção de dados e deseja que ela seja percorrida com foreach, implementar IteratorAggregate é a abordagem recomendada. Essa interface também permite que você troque a lógica de iteração sem alterar a classe principal, promovendo a flexibilidade.
Boas práticas
- Prefira
IteratorAggregateaIteratorquando não precisar de controle manual do ponteiro. - Use
yieldem generators para criar iteradores de forma concisa e legível. - Aproveite as classes SPL prontas para evitar reinventar a roda.
- Lembre-se de que
foreachfunciona com qualquer objeto que implementeTraversable. - Teste seus iteradores com vários cenários, incluindo coleções vazias.
Referências
- PHP: Iterator - Manual
- PHP: IteratorAggregate - Manual
- PHP: ArrayObject - Manual
- PHP: SplStack - Manual
- PHP: Generators - Manual
- PHP: SPL - Manual
Exercícios
Crie uma classe
RangeIteratorque implementaIteratore itera de um valor inicial até um valor final, incrementando de 1 em 1. Teste com umforeachde 1 a 5.✓ Resposta:<?php class RangeIterator implements Iterator { private int $start; private int $end; private int $current; public function __construct(int $start, int $end) { $this->start = $start; $this->end = $end; $this->current = $start; } public function current(): int { return $this->current; } public function key(): int { return $this->current - $this->start; } public function next(): void { $this->current++; } public function rewind(): void { $this->current = $this->start; } public function valid(): bool { return $this->current <= $this->end; } } $range = new RangeIterator(1, 5); foreach ($range as $key => $value) { echo "$key => $value\n"; }Implemente uma classe
ProductCollectionque implementaIteratorAggregatee itera sobre uma lista de produtos (use um array de strings). Use umGeneratorpara retornar os produtos.✓ Resposta:<?php class ProductCollection implements IteratorAggregate { private array $products; public function __construct(array $products) { $this->products = $products; } public function getIterator(): Traversable { foreach ($this->products as $product) { yield $product; } } } $collection = new ProductCollection(['Notebook', 'Mouse', 'Teclado']); foreach ($collection as $product) { echo $product . PHP_EOL; }Usando
ArrayObject, crie um array de nomes de frutas e adicione uma nova fruta. Depois, itere e imprima cada fruta em maiúsculas.✓ Resposta:<?php $fruits = new ArrayObject(['maçã', 'banana', 'laranja']); $fruits[] = 'uva'; foreach ($fruits as $fruit) { echo strtoupper($fruit) . PHP_EOL; } // MAÇÃ // BANANA // LARANJA // UVACrie uma pilha
SplStackcom os números 1, 2, 3. Empilhe o número 4 e depois desempilhe um elemento. Mostre o topo da pilha após cada operação.✓ Resposta:<?php $stack = new SplStack(); $stack->push(1); $stack->push(2); $stack->push(3); echo $stack->top() . PHP_EOL; // 3 $stack->push(4); echo $stack->top() . PHP_EOL; // 4 $stack->pop(); // remove 4 echo $stack->top() . PHP_EOL; // 3Escreva uma função que recebe um array e retorna um
ArrayIteratorque percorre os elementos em ordem reversa. Dica: usearray_reverseeArrayIterator.✓ Resposta:<?php function reverseIterator(array $array): ArrayIterator { return new ArrayIterator(array_reverse($array)); } $it = reverseIterator([1, 2, 3]); foreach ($it as $value) { echo $value . PHP_EOL; // 3, 2, 1 }