Por que Generics não são apenas uma tendência, mas uma necessidade?
\n\nSe você já escreveu código PHP que trabalhava com coleções, repositórios ou camadas de serviço, certamente já passou por esta situação: a função aceita um array, mas o que tem dentro é um mistério. Podem ser usuários, podem ser gatos. Ou, pior ainda, os dois juntos. É exatamente aqui que os Generics entram em cena — uma ferramenta poderosa de tipagem que torna seu código previsível, seguro e autodocumentado.
Neste artigo, vamos explorar como os generics funcionam no PHP, por que você precisa deles e como usá-los em projetos reais. Você aprenderá a evitar erros comuns e a escrever um código mais profissional. Vamos nessa!
\n\nO que são Generics em termos simples?
\n\nImagine que você é um desenvolvedor de e-commerce. Você tem uma classe Carrinho que armazena produtos. Sem generics, você escreve:
class Carrinho {\n private array $itens;\n \n public function adicionarItem($item): void {\n $this->itens[] = $item;\n }\n \n public function getItens(): array {\n return $this->itens;\n }\n}\n\nQual o problema? $item pode ser qualquer coisa — número, string, objeto Produto ou até null. A IDE não consegue sugerir qual tipo é retornado por getItens(). Você vai ter que consultar a documentação ou debugar.
Generics resolvem isso. Eles permitem que você especifique: "Ei, esta classe só trabalha com produtos!". Fica assim:
\n\nclass Carrinho {\n /** @param Produto[] $itens */\n private array $itens;\n \n public function adicionarItem(Produto $item): void {\n $this->itens[] = $item;\n }\n \n /** @return Produto[] */\n public function getItens(): array {\n return $this->itens;\n }\n}\n\nMas isso é apenas uma simulação. Os generics reais no PHP surgiram com o PHP 8.0, e o suporte completo veio no PHP 8.1+ com atributos e análise estática (como PHPStan ou Psalm).
\n\nComo os Generics funcionam de verdade no PHP?
\n\nAo contrário de Java ou C#, o PHP não possui suporte nativo a generics no nível de sintaxe (por enquanto). No entanto, graças às anotações PHPDoc e ferramentas de análise estática, podemos obter todos os benefícios da tipagem.
\n\nTipos Template (Template Types)
\n\nA abordagem mais popular é usar @template no PHPDoc. Veja um exemplo com uma coleção:
/**\n * @template T\n */\nclass Colecao {\n /** @var array<int, T> */\n private array $itens = [];\n \n /**\n * @param T $item\n */\n public function adicionar(mixed $item): void {\n $this->itens[] = $item;\n }\n \n /**\n * @return T\n */\n public function get(int $indice): mixed {\n return $this->itens[$indice] ?? throw new \\OutOfBoundsException();\n }\n}\n\nAgora, ao usar esta classe, você especifica o tipo concreto:
\n\n/** @var Colecao<Usuario> $colecaoUsuarios */\n$colecaoUsuarios = new Colecao();\n$colecaoUsuarios->adicionar(new Usuario('Alice'));\n$usuario = $colecaoUsuarios->get(0); // PHPStan sabe: $usuario é do tipo Usuario\necho $usuario->nome; // Seguro!\n\nRestrições (Constraints) para Generics
\n\nÀs vezes, você precisa que o tipo T não seja qualquer um, mas que implemente uma interface específica. Isso é chamado de bound (restrição):
\n\n/**\n * @template T of \\JsonSerializable\n */\nclass ColecaoJson {\n /** @var array<int, T> */\n private array $itens = [];\n \n public function paraJson(): string {\n return json_encode($this->itens);\n }\n}\n\nAgora você não conseguirá adicionar um objeto que não implemente JsonSerializable à coleção. A IDE e o analisador estático exibirão um erro.