Interfaces são o principal mecanismo de abstração do PHP orientado a objetos. Você já sabe declarar uma interface e implementá-la — mas o uso profissional vai muito além disso. Neste artigo exploramos interfaces como contratos formais de comportamento, a diferença entre covariância e contravariância nos tipos de retorno e parâmetros, interfaces funcionais com Closures, segregação de interfaces (o "I" do SOLID), e como compor comportamentos complexos empilhando interfaces pequenas.
Esses conceitos são o que separa um sistema que "funciona" de um sistema que pode crescer sem dor: código que depende de interfaces, não de implementações, pode ter suas partes substituídas, testadas e estendidas de forma independente.
Interfaces como contratos formais
Uma interface é um contrato: quem implementa promete que o código cliente pode chamar esses métodos com esses tipos e receber esses retornos — independente de qual classe concreta esteja por baixo. O poder não está na interface em si, mas na inversão de dependência que ela permite: o código de alto nível depende da abstração, não da implementação.
<?php
declare(strict_types=1);
namespace MeuApp\Contratos;
// Interface como contrato — define o que, não o como
interface RepositorioInterface
{
public function buscarPorId(int $id): ?array;
public function buscarTodos(): array;
public function salvar(array $dados): int; // retorna o ID gerado
public function deletar(int $id): bool;
}
// Implementação para produção — usa MySQL
class ProdutoRepositorioMysql implements RepositorioInterface
{
public function __construct(private readonly \PDO $pdo) {}
public function buscarPorId(int $id): ?array
{
$stmt = $this->pdo->prepare("SELECT * FROM produtos WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch(\PDO::FETCH_ASSOC) ?: null;
}
public function buscarTodos(): array
{
return $this->pdo->query("SELECT * FROM produtos")->fetchAll(\PDO::FETCH_ASSOC);
}
public function salvar(array $dados): int
{
$stmt = $this->pdo->prepare("INSERT INTO produtos (nome, preco) VALUES (?, ?)");
$stmt->execute([$dados['nome'], $dados['preco']]);
return (int) $this->pdo->lastInsertId();
}
public function deletar(int $id): bool
{
$stmt = $this->pdo->prepare("DELETE FROM produtos WHERE id = ?");
$stmt->execute([$id]);
return $stmt->rowCount() > 0;
}
}
// Implementação para testes — em memória, sem banco de dados
class ProdutoRepositorioEmMemoria implements RepositorioInterface
{
private array $dados = [];
private int $proximoId = 1;
public function buscarPorId(int $id): ?array
{
return $this->dados[$id] ?? null;
}
public function buscarTodos(): array
{
return array_values($this->dados);
}
public function salvar(array $dado): int
{
$id = $this->proximoId++;
$this->dados[$id] = array_merge($dado, ['id' => $id]);
return $id;
}
public function deletar(int $id): bool
{
if (!isset($this->dados[$id])) return false;
unset($this->dados[$id]);
return true;
}
}
// O serviço depende da INTERFACE, não de nenhuma implementação concreta
// Em produção recebe ProdutoRepositorioMysql
// Em testes recebe ProdutoRepositorioEmMemoria
// O código do serviço não muda — só a implementação injetada muda
class ProdutoService
{
public function __construct(
private readonly RepositorioInterface $repositorio,
) {}
public function listar(): array
{
return $this->repositorio->buscarTodos();
}
public function criar(string $nome, float $preco): int
{
if (empty($nome)) {
throw new \InvalidArgumentException("Nome do produto não pode ser vazio.");
}
return $this->repositorio->salvar(['nome' => $nome, 'preco' => $preco]);
}
}
Covariância e contravariância
PHP 7.4+ suporta tipos covariantes em retornos e contravariantes em parâmetros. Esses conceitos governam como os tipos podem variar quando uma subclasse ou implementação sobrescreve métodos da interface pai.
Covariância (retorno): uma classe filha pode retornar um tipo mais específico que o declarado na interface/pai. Se a interface declara buscar(): Animal, a implementação pode retornar buscar(): Cachorro — desde que Cachorro extends Animal.
Contravariância (parâmetro): uma classe filha pode aceitar um tipo mais amplo que o declarado na interface/pai. Se a interface declara alimentar(Cachorro $c), a implementação pode aceitar alimentar(Animal $a) — mais permissiva, nunca mais restritiva.
<?php
declare(strict_types=1);
// Hierarquia de tipos para demonstrar covariância/contravariância
abstract class Animal { abstract public function som(): string; }
class Cachorro extends Animal { public function som(): string { return "Au!"; } }
class Gato extends Animal { public function som(): string { return "Miau!"; } }
// Interface base
interface FabricaAnimalInterface
{
public function criar(): Animal; // retorno: Animal
public function alimentar(Animal $a): void; // parâmetro: Animal
}
// ── COVARIÂNCIA — retorno mais específico ──────────────────────────────
// PHP 7.4+: implementação pode retornar tipo mais específico que a interface
class FabricaCachorro implements FabricaAnimalInterface
{
// ✅ Covariante: Cachorro é mais específico que Animal (ok)
public function criar(): Cachorro
{
return new Cachorro();
}
// ✅ Contravariante: Animal é mais amplo que Cachorro (ok)
// Esta fábrica aceita qualquer Animal — não só Cachorro
public function alimentar(Animal $a): void
{
echo "Alimentando " . get_class($a) . ": " . $a->som() . "\n";
}
}
class FabricaGato implements FabricaAnimalInterface
{
// ✅ Covariante: Gato é mais específico que Animal
public function criar(): Gato
{
return new Gato();
}
public function alimentar(Animal $a): void
{
echo "Alimentando " . get_class($a) . ": " . $a->som() . "\n";
}
}
// O cliente usa FabricaAnimalInterface — não sabe qual fábrica concreta é
function criarEAlimentar(FabricaAnimalInterface $fabrica): void
{
$animal = $fabrica->criar();
$fabrica->alimentar($animal);
}
criarEAlimentar(new FabricaCachorro()); // Alimentando Cachorro: Au!
criarEAlimentar(new FabricaGato()); // Alimentando Gato: Miau!
Na prática, covariância é muito comum ao trabalhar com repositórios e factories. Uma RepositorioUsuarioInterface pode declarar buscarPorId(): ?Usuario onde a implementação concreta retorna ?UsuarioAtivo — uma subclasse de Usuario.
Interface Segregation Principle — ISP
O "I" do SOLID diz: um cliente não deve ser forçado a depender de métodos que não usa. Uma interface grande demais obriga implementações a definir métodos vazios ou lançar UnsupportedOperationException — sinal claro de violação do ISP.
A solução é compor comportamentos a partir de interfaces pequenas e focadas:
<?php
declare(strict_types=1);
// ❌ Interface gorda — viola ISP
// Um repositório somente leitura é forçado a implementar salvar() e deletar()
interface RepositorioGordoInterface
{
public function buscarPorId(int $id): ?array;
public function buscarTodos(): array;
public function salvar(array $dados): int;
public function deletar(int $id): bool;
public function paginar(int $pagina, int $porPagina): array;
public function buscarPorFiltro(array $filtros): array;
public function contarTotal(): int;
}
// ✅ Interfaces segregadas — cada cliente usa só o que precisa
interface LegivelInterface
{
public function buscarPorId(int $id): ?array;
public function buscarTodos(): array;
}
interface GravavelInterface
{
public function salvar(array $dados): int;
public function deletar(int $id): bool;
}
interface PaginavelInterface
{
public function paginar(int $pagina, int $porPagina): array;
public function contarTotal(): int;
}
interface FiltravelInterface
{
public function buscarPorFiltro(array $filtros): array;
}
// Repositório completo implementa todas as interfaces necessárias
class ProdutoRepositorio implements LegivelInterface, GravavelInterface, PaginavelInterface, FiltravelInterface
{
// implementa todos os métodos
public function buscarPorId(int $id): ?array { return null; }
public function buscarTodos(): array { return []; }
public function salvar(array $dados): int { return 1; }
public function deletar(int $id): bool { return true; }
public function paginar(int $p, int $pp): array { return []; }
public function contarTotal(): int { return 0; }
public function buscarPorFiltro(array $f): array { return []; }
}
// Repositório somente leitura — implementa apenas o necessário
class RelatorioRepositorio implements LegivelInterface, PaginavelInterface, FiltravelInterface
{
public function buscarPorId(int $id): ?array { return null; }
public function buscarTodos(): array { return []; }
public function paginar(int $p, int $pp): array { return []; }
public function contarTotal(): int { return 0; }
public function buscarPorFiltro(array $f): array { return []; }
// ✅ Não implementa salvar() nem deletar() — não precisa
}
// Serviços dependem apenas das interfaces que realmente usam
class ExportadorRelatorio
{
// Precisa ler e paginar — não precisa escrever
public function __construct(
private readonly LegivelInterface&PaginavelInterface $repositorio,
) {}
}
class AdministracaoProdutos
{
// Precisa de tudo
public function __construct(
private readonly LegivelInterface&GravavelInterface $repositorio,
) {}
}
A sintaxe LegivelInterface&PaginavelInterface é uma intersection type do PHP 8.1 — o parâmetro aceita apenas objetos que implementam ambas as interfaces simultaneamente.
Interfaces com constantes e herança de interfaces
Interfaces podem declarar constantes (implicitamente public final) e podem estender outras interfaces — inclusive múltiplas ao mesmo tempo. Isso permite construir hierarquias de contratos sem herança de implementação.
<?php
declare(strict_types=1);
// Interface com constantes — úteis para valores do domínio
interface StatusPedidoInterface
{
const PENDENTE = 'pendente';
const APROVADO = 'aprovado';
const CANCELADO = 'cancelado';
const ENTREGUE = 'entregue';
public function status(): string;
public function podeSerCancelado(): bool;
}
// Interface estendendo outra — herança de contrato
interface PedidoRastreavel extends StatusPedidoInterface
{
public function codigoRastreamento(): ?string;
public function atualizarLocalizacao(string $local): void;
}
// Interface estendendo múltiplas — composição de contratos
interface PedidoCompletoInterface extends StatusPedidoInterface, PedidoRastreavel
{
public function valorTotal(): float;
public function itens(): array;
}
// Uma classe que implementa PedidoCompletoInterface
// é obrigada a implementar TODOS os métodos das três interfaces
class Pedido implements PedidoCompletoInterface
{
private string $status = StatusPedidoInterface::PENDENTE;
private ?string $rastreamento = null;
private array $historico = [];
public function __construct(
private readonly array $itens,
private readonly float $total,
) {}
public function status(): string { return $this->status; }
public function valorTotal(): float { return $this->total; }
public function itens(): array { return $this->itens; }
public function codigoRastreamento(): ?string { return $this->rastreamento; }
public function podeSerCancelado(): bool
{
return in_array($this->status, [
StatusPedidoInterface::PENDENTE,
StatusPedidoInterface::APROVADO,
]);
}
public function atualizarLocalizacao(string $local): void
{
$this->historico[] = ['local' => $local, 'em' => date('Y-m-d H:i:s')];
echo "📍 Pedido em: {$local}\n";
}
}
Interfaces funcionais e Closures
PHP não tem interfaces funcionais como Java (interfaces com um único método abstrato, usadas como lambdas), mas o padrão existe implicitamente: qualquer interface com um único método pode ser substituída por uma Closure quando a flexibilidade for mais importante que a nomeação explícita.
<?php
declare(strict_types=1);
// Interface funcional — um único método abstrato
interface TransformadorInterface
{
public function transformar(mixed $valor): mixed;
}
// Implementação com classe — útil quando tem estado ou precisa ser injetada
class UpperCaseTransformador implements TransformadorInterface
{
public function transformar(mixed $valor): mixed
{
return is_string($valor) ? strtoupper($valor) : $valor;
}
}
// Pipeline que aceita a interface
class Pipeline
{
/** @var TransformadorInterface[] */
private array $etapas = [];
public function pipe(TransformadorInterface $t): static
{
$this->etapas[] = $t;
return $this;
}
// Também aceita Closure — adaptador inline para a interface
public function pipeCallback(callable $fn): static
{
// Closure adaptada para a interface — padrão Adapter em miniatura
return $this->pipe(new class($fn) implements TransformadorInterface {
public function __construct(private readonly \Closure $fn) {}
public function transformar(mixed $valor): mixed
{
return ($this->fn)($valor);
}
});
}
public function processar(mixed $entrada): mixed
{
return array_reduce(
$this->etapas,
fn($carry, TransformadorInterface $t) => $t->transformar($carry),
$entrada
);
}
}
$resultado = (new Pipeline())
->pipe(new UpperCaseTransformador())
->pipeCallback(fn($v) => trim($v))
->pipeCallback(fn($v) => str_replace(' ', '-', $v))
->processar(" olá mundo ");
echo $resultado . "\n";
// OLÁ-MUNDO
Resumo do artigo
| Conceito | O que aprendemos |
|---|---|
| Interface como contrato | Desacopla alto nível de implementações concretas — permite troca sem alterar código cliente |
| Covariância | Retorno pode ser tipo mais específico que o declarado na interface |
| Contravariância | Parâmetro pode ser tipo mais amplo que o declarado na interface |
| ISP | Interfaces pequenas e focadas — o cliente depende só do que usa |
| Intersection types | A&B — objeto deve implementar ambas as interfaces |
| Herança de interfaces | Uma interface pode estender múltiplas — composição de contratos |
| Constantes em interfaces | Implicitamente public final — valores de domínio compartilhados |
| Interface funcional | Interface com um único método — pode ser adaptada para Closure |
Exercício da semana
-
Crie as interfaces
LogavelInterface(comlog(string $nivel, string $mensagem): void),FormatavelInterface(comformatar(array $dados): string) eEnviavelInterface(comenviar(string $destino, string $conteudo): bool). Implemente umNotificadorque recebe as três interfaces e as combina para logar, formatar e enviar notificações. -
Demonstre covariância: crie uma interface
CriadorInterfacecomcriar(): Veiculo. ImplementeCriadorCarroretornandoCarro extends VeiculoeCriadorMotoretornandoMoto extends Veiculo. Verifique que o código cliente usandoCriadorInterfacefunciona com ambas sem modificação. -
Refatore um repositório gordo em interfaces segregadas:
BuscavelInterface,SalvavelInterface,DeletavelInterface. Crie um repositório de auditoria que implementa apenasBuscavelInterface— sem métodos desnecessários. -
Construa um mini-pipeline de processamento de pedidos usando a interface
EtapaPedidoInterfacecomprocessar(array $pedido): array. Implemente três etapas:ValidarEstoque,CalcularFrete,AplicarDesconto. O pipeline executa todas em sequência e retorna o pedido processado. -
Desafio: use intersection types para criar um
RelatorioServiceque aceitaLegivelInterface&FiltravelInterface&PaginavelInterface. Implemente duas classes que satisfazem essas interfaces de formas diferentes (uma com array em memória, outra simulando consulta ao banco) e verifique que ambas podem ser injetadas no serviço.
Referências
- PHP Manual — Interfaces: https://www.php.net/manual/pt_BR/language.oop5.interfaces.php
- PHP Manual — Intersection Types (PHP 8.1): https://www.php.net/manual/pt_BR/language.types.intersection.php
- PHP Manual — Covariance and Contravariance: https://www.php.net/manual/pt_BR/language.oop5.variance.php
- MARTIN, R.C. — Interface Segregation Principle. https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/isp.pdf
- Refactoring Guru — Dependency Inversion Principle (pt-BR): https://refactoring.guru/pt-br/dependency-inversion-principle
- PHP: The Right Way — Design Patterns: https://phptherightway.com/#design_patterns