PHP

Interfaces Avançadas Já leu

12 min de leitura

Interfaces Avançadas
Interfaces são o principal mecanismo de abstração do PHP orientado a objetos. Você já sabe declarar uma interface e implement&

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

  1. Crie as interfaces LogavelInterface (com log(string $nivel, string $mensagem): void), FormatavelInterface (com formatar(array $dados): string) e EnviavelInterface (com enviar(string $destino, string $conteudo): bool). Implemente um Notificador que recebe as três interfaces e as combina para logar, formatar e enviar notificações.

  2. Demonstre covariância: crie uma interface CriadorInterface com criar(): Veiculo. Implemente CriadorCarro retornando Carro extends Veiculo e CriadorMoto retornando Moto extends Veiculo. Verifique que o código cliente usando CriadorInterface funciona com ambas sem modificação.

  3. Refatore um repositório gordo em interfaces segregadas: BuscavelInterface, SalvavelInterface, DeletavelInterface. Crie um repositório de auditoria que implementa apenas BuscavelInterface — sem métodos desnecessários.

  4. Construa um mini-pipeline de processamento de pedidos usando a interface EtapaPedidoInterface com processar(array $pedido): array. Implemente três etapas: ValidarEstoque, CalcularFrete, AplicarDesconto. O pipeline executa todas em sequência e retorna o pedido processado.

  5. Desafio: use intersection types para criar um RelatorioService que aceita LegivelInterface&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
Comentários

Mais em PHP

Estruturas de Repetição
Estruturas de Repetição

Se as estruturas de controle ensinam o programa a tomar decis&otilde;es, as e...

A História do PHP: de script pessoal a pilar da web
A História do PHP: de script pessoal a pilar da web

Poucas linguagens de programa&ccedil;&atilde;o t&ecirc;m uma origem t&atilde;...

O que é PHP e por que ele ainda importa
O que é PHP e por que ele ainda importa

Quando voc&ecirc; acessa um site, preenche um formul&aacute;rio, faz login em...