Trabalhando com Coleções em PHP
O PHP é uma linguagem "tipada", porém é de tipagem fraca. Durante muito tempo, para trabalharmos com coleções no PHP era necessário utilizar apenas arrays (chave-valor) sem tipo. Isso funciona bem para dados simples, como uma lista de IDs, mas quando lidamos com listas de objetos ou queremos garantir a integridade de dados, o uso de arrays pode trazer algumas limitações.
Por exemplo, imagine que você está implementando a classe ShoppingCart, que deve manipular uma lista de Product. Se você usar um array, ele será transportado apenas como um array em qualquer lugar do código, sem restrições sobre o que pode ser adicionado. Além disso, arrays no PHP são passados por cópia, e não por referência, o que pode impactar a performance e dificultar o controle de estado.
Aqui está um exemplo básico da classe Product:
<?php
class Product
{
public function __construct(
public readonly string $name,
public readonly float $price
) {}
}Agora, veja como poderia ser a classe ShoppingCart:
<?php
class ShoppingCart
{
private array $products = [];
public function __construct(array $products = [])
{
$this->products = $products;
}
public function add(Product $product): void
{
$this->products[] = $product;
}
public function all(): array
{
return $this->products;
}
}Embora o método add() garanta que apenas instâncias de Product sejam adicionadas, o mesmo não acontece no construtor ou no método all(). Esse problema pode ser parcialmente resolvido com validações adicionais, mas isso leva a redundância de código e ainda não resolve o problema da ausência de referência.
Criando coleções
O PHP fornece interfaces que permitem criar classes que atuam como coleções com comportamento mais robusto, como Iterator e ArrayAccess. Vamos explorar como elas podem ser utilizadas para resolver os problemas mencionados.
Interface Iterator
A interface Iterator é útil quando você deseja que um objeto seja percorrido com um foreach. Ela exige a implementação de cinco métodos:
current(): Retorna o elemento atual.key(): Retorna a chave (índice) atual.next(): Move para o próximo elemento.rewind(): Reinicia a iteração.valid(): Verifica se a posição atual é válida.
Veja como implementar a interface Iterator para criar uma coleção de Product:
<?php
class ProductCollection implements \Iterator
{
private array $items = [];
private int $position = 0;
public function add(Product $item): void
{
$this->items[] = $item;
}
public function current(): mixed
{
return $this->items[$this->position];
}
public function key(): int
{
return $this->position;
}
public function next(): void
{
$this->position++;
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->items[$this->position]);
}
}Agora, ProductCollection pode ser usada de forma segura e iterável:
<?php
$collection = new ProductCollection();
$collection->add(new Product('Item 1'));
$collection->add(new Product('Item 2'));
$collection->add(new Product('Item 3'));
foreach ($collection as $product) {
echo $product->name . PHP_EOL;
}Interface ArrayAccess
A interface ArrayAccess permite que um objeto seja tratado como um array. Isso é útil se você prefere a sintaxe de colchetes ([]) para manipular os dados da coleção. Os métodos obrigatórios são:
offsetExists($offset): Verifica se índice existe.offsetGet($offset): Retorna de um índice.offsetSet($offset, $value): Define um valor para um índice.offsetUnset($offset): Remove um índice.
Agora vamos implementar a interface ArrayAccess na nossa classe ProductCollection:
<?php
class ProductCollection implements \Iterator, \ArrayAccess
{
private array $products = [];
private int $position = 0;
public function current(): mixed
{
return $this->products[$this->position];
}
public function key(): int
{
return $this->position;
}
public function next(): void
{
$this->position++;
}
public function rewind(): void
{
$this->position = 0;
}
public function valid(): bool
{
return isset($this->products[$this->position]);
}
public function offsetExists($offset): bool
{
return isset($this->products[$offset]);
}
public function offsetGet($offset): mixed
{
return $this->products[$offset] ?? null;
}
public function offsetSet($offset, $value): void
{
if (! $value instanceof Product) {
throw new \InvalidArgumentException('Only instances of Product are allowed.');
}
if ($offset === null) {
$this->products[] = $value;
} else {
$this->products[$offset] = $value;
}
}
public function offsetUnset($offset): void
{
unset($this->products[$offset]);
}
}A nossa classe ProductCollection agora pode ter tratada como um array, mas ela tem restrição de permitir somente Product. Veja como fica a utilização dela:
<?php
$collection = new ProductCollection();
$collection[] = new Product("Produto 1");
$collection[] = new Product("Produto 2");
foreach ($collection as $product) {
echo $product->name . PHP_EOL;
}
echo $collection[0]->name . PHP_EOL;
unset($collection[1]);
var_dump(isset($collection[1]));
try {
$collection[] = "Texto inválido";
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}Refatorando ShoppingCart
Com a ProductCollection, a classe ShoppingCart pode ser reescrita para ser mais robusta e confiável:
<?php
class ShoppingCart
{
private ProductCollection $productCollection;
public function __construct(
?ProductCollection $productCollection = null
) {
$this->productCollection = $productCollection ?? new ProductCollection();
}
public function add(Product $product): void
{
$this->productCollection[] = $product;
}
public function all(): ProductCollection
{
return $this->productCollection;
}
}Agora, o ShoppingCart utiliza ProductCollection, garantindo a integridade da lista de produtos. Sua aplicação se torna mais confiável, legível e compatível com IDEs para autocompletar e análise de código.
Conclusão
O uso de coleções no PHP, com o suporte de interfaces como Iterator e ArrayAccess, permite criar estruturas de dados mais seguras e flexíveis. Essas práticas ajudam a evitar erros comuns, aumentam a legibilidade do código e garantem a integridade dos dados, especialmente em projetos complexos. Considere usar essas abordagens sempre que possível para melhorar a qualidade do seu código.