Interfaces são um dos pilares da programação orientada a objetos em PHP. Elas permitem definir um conjunto de métodos que uma classe deve implementar, sem especificar como esses métodos funcionam. Isso cria um contrato entre a interface e as classes que a implementam, garantindo que todas as classes que usam a interface tenham os mesmos métodos públicos, prontos para serem chamados de forma padronizada.

Nesta aula, vamos explorar desde a sintaxe básica de uma interface até o uso de múltiplas interfaces e o conceito de contratos. Você verá como as interfaces promovem o desacoplamento entre componentes, facilitam a manutenção e permitem que o código seja mais testável e extensível.

Interface

Em PHP, uma interface é definida com a palavra-chave interface. Dentro dela, declaramos assinaturas de métodos (sem corpo) que devem ser públicos. Interfaces também podem conter constantes, mas não podem ter propriedades (atributos) nem métodos privados ou protegidos. Elas são como um esqueleto que define o que uma classe deve fazer, não como fazer.

Vejamos um exemplo simples: imagine que temos diferentes tipos de animais que podem emitir sons. Podemos criar uma interface EmitirSom com um método emitirSom().

<?php

interface EmitirSom {
    public function emitirSom(): string;
}

?>

Aqui, definimos que qualquer classe que implemente EmitirSom deve ter um método público emitirSom() que retorna uma string. Note que não há implementação, apenas a assinatura. Isso obriga as classes a fornecerem a lógica específica para cada animal.

Interfaces são especialmente úteis para definir capacidades que podem ser compartilhadas por classes não relacionadas. Por exemplo, um carro e uma bicicleta podem ambos implementar uma interface Movivel com um método mover(), mesmo que a forma de mover seja completamente diferente.

implements

Para que uma classe use uma interface, utilizamos a palavra-chave implements. A classe deve implementar todos os métodos declarados na interface, respeitando as assinaturas (incluindo tipos de parâmetros e retorno). Caso contrário, um erro fatal será lançado.

Vamos implementar a interface EmitirSom em duas classes: Cachorro e Gato.

<?php

class Cachorro implements EmitirSom {
    public function emitirSom(): string {
        return "Au au!";
    }
}

class Gato implements EmitirSom {
    public function emitirSom(): string {
        return "Miau!";
    }
}

// Uso
$animais = [new Cachorro(), new Gato()];
foreach ($animais as $animal) {
    echo $animal->emitirSom() . PHP_EOL;
}
// Saída:
// Au au!
// Miau!

?>

Observe que tanto Cachorro quanto Gato implementam o mesmo método, mas cada um retorna seu som característico. Agora podemos tratar todos os objetos que implementam EmitirSom de forma uniforme, chamando o mesmo método sem nos preocuparmos com a classe concreta.

Uma classe pode implementar várias interfaces, como veremos a seguir, e também pode estender uma classe pai e implementar interfaces simultaneamente.

Múltiplas interfaces

PHP permite que uma classe implemente mais de uma interface. Isso é útil quando queremos combinar comportamentos de diferentes fontes. Para isso, basta listar as interfaces separadas por vírgula após implements.

Suponha que temos duas interfaces: Movivel e EmitirSom. Um robô pode se mover e emitir sons, então vamos criar uma classe Robo que implementa ambas.

<?php

interface Movivel {
    public function mover(): void;
}

interface EmitirSom {
    public function emitirSom(): string;
}

class Robo implements Movivel, EmitirSom {
    public function mover(): void {
        echo "Robô se movendo...\n";
    }

    public function emitirSom(): string {
        return "Beep beep!";
    }
}

$robo = new Robo();
$robo->mover();
// Robô se movendo...
echo $robo->emitirSom();
// Beep beep!

?>

Ao implementar múltiplas interfaces, a classe assume todos os contratos. Isso pode tornar a classe mais robusta, mas também aumenta a responsabilidade, pois ela precisa fornecer implementações para todos os métodos declarados em todas as interfaces.

É importante notar que as interfaces podem herdar de outras interfaces usando extends. Por exemplo, podemos ter uma interface Veiculo que estende Movivel e adiciona um método abastecer(). Uma classe que implementa Veiculo deve implementar todos os métodos de Veiculo e também de Movivel.

Contratos

O conceito de contrato em programação refere-se a um acordo entre o chamador e o implementador sobre o que uma função ou método fará. Interfaces são a materialização desse contrato no PHP: elas definem a assinatura pública que as classes devem seguir, garantindo que quem usa a classe saiba exatamente quais métodos estão disponíveis e quais tipos de entrada e saída esperar.

Isso traz vários benefícios:

  • Desacoplamento: o código que depende de uma interface não precisa conhecer a implementação concreta, apenas a interface. Isso permite trocar implementações sem afetar o restante do sistema.
  • Testabilidade: com interfaces, é fácil criar mocks ou fakes em testes unitários, pois podemos simular o comportamento da interface sem instanciar a classe real.
  • Extensibilidade: novas classes podem ser adicionadas implementando a mesma interface, sem modificar o código existente (Princípio Aberto/Fechado).
  • Padronização: interfaces definem um padrão para que diferentes implementações sejam usadas de forma intercambiável.

Um exemplo clássico de contrato é o uso de interfaces em repositórios de dados. Suponha que temos uma interface UsuarioRepository com métodos find($id) e save(Usuario $usuario). Podemos ter uma implementação com MySQL e outra com arquivo JSON. O serviço que usa o repositório depende apenas da interface, então podemos alternar entre as implementações facilmente.

<?php

interface UsuarioRepository {
    public function find(int $id): ?Usuario;
    public function save(Usuario $usuario): void;
}

class MySqlUsuarioRepository implements UsuarioRepository {
    public function find(int $id): ?Usuario {
        // busca no banco
    }
    public function save(Usuario $usuario): void {
        // salva no banco
    }
}

class JsonUsuarioRepository implements UsuarioRepository {
    public function find(int $id): ?Usuario {
        // busca no arquivo JSON
    }
    public function save(Usuario $usuario): void {
        // salva no arquivo JSON
    }
}

// O serviço depende da interface, não da implementação concreta
class UsuarioService {
    private UsuarioRepository $repository;

    public function __construct(UsuarioRepository $repository) {
        $this->repository = $repository;
    }

    public function getUsuario(int $id): ?Usuario {
        return $this->repository->find($id);
    }
}

// Podemos injetar qualquer implementação
$service = new UsuarioService(new MySqlUsuarioRepository());
// ou $service = new UsuarioService(new JsonUsuarioRepository());

?>

Esse padrão é amplamente utilizado em frameworks modernos como Laravel e Symfony, onde os serviços são programados contra interfaces, e o contêiner de injeção de dependência resolve qual implementação usar em tempo de execução.

É fundamental entender que interfaces não contêm lógica; elas apenas definem o contrato. A implementação fica a cargo das classes. Isso ajuda a manter o código organizado e claro, pois os contratos são explícitos e verificáveis pelo próprio PHP.

Boas práticas e observações finais

Ao trabalhar com interfaces, siga estas boas práticas:

  • Prefira depender de interfaces em vez de classes concretas (Inversão de Dependência).
  • Mantenha as interfaces pequenas e coesas, com um propósito claro. Se uma interface cresce demais, considere dividi-la em várias menores (Princípio da Segregação de Interfaces).
  • Use nomes que indiquem capacidade ou contrato, como Serializable, Jsonable, ou LoggerInterface.
  • Documente as interfaces com PHPDoc para deixar claro o contrato de cada método.
  • Lembre-se de que interfaces podem ser usadas em type hints, o que torna o código mais expressivo e seguro.

Interfaces são uma ferramenta poderosa para criar sistemas flexíveis e de fácil manutenção. Dominar seu uso é essencial para qualquer desenvolvedor PHP que busca escrever código profissional.

Exercícios

  1. Crie uma interface chamada FormaGeometrica com métodos calcularArea(): float e calcularPerimetro(): float. Em seguida, implemente essa interface em duas classes: Circulo (que recebe o raio no construtor) e Retangulo (que recebe base e altura). Teste as classes criando objetos e exibindo área e perímetro.
  2. Explique o que aconteceria se uma classe tentasse implementar uma interface sem fornecer a implementação de todos os métodos. Ilustre com um exemplo de código que gera erro.
  3. Crie duas interfaces: Imprimivel com método imprimir(): string e Exportavel com método exportar(): array. Implemente ambas em uma classe Relatorio que tenha propriedades titulo e conteudo. O método imprimir() deve retornar o relatório em formato textual, e exportar() deve retornar um array com as propriedades.
  4. Escreva um código que demonstre o uso de uma interface como type hint em uma função. Crie uma função processarEmitirSom(EmitirSom $objeto) que chama o método emitirSom() e imprime o resultado. Use com objetos de diferentes classes que implementam a interface.
  5. Considere o seguinte código: interface A { public function foo(); } interface B extends A { public function bar(); } class C implements B { public function bar() {} } Por que a classe C está incorreta? O que é necessário para corrigir? Explique e reescreva a classe C corretamente.

✓ Resposta:
<?php

interface FormaGeometrica {
    public function calcularArea(): float;
    public function calcularPerimetro(): float;
}

class Circulo implements FormaGeometrica {
    private float $raio;

    public function __construct(float $raio) {
        $this->raio = $raio;
    }

    public function calcularArea(): float {
        return pi() * $this->raio ** 2;
    }

    public function calcularPerimetro(): float {
        return 2 * pi() * $this->raio;
    }
}

class Retangulo implements FormaGeometrica {
    private float $base;
    private float $altura;

    public function __construct(float $base, float $altura) {
        $this->base = $base;
        $this->altura = $altura;
    }

    public function calcularArea(): float {
        return $this->base * $this->altura;
    }

    public function calcularPerimetro(): float {
        return 2 * ($this->base + $this->altura);
    }
}

$circulo = new Circulo(2.0);
echo 'Área do círculo: ' . $circulo->calcularArea() . PHP_EOL;
echo 'Perímetro do círculo: ' . $circulo->calcularPerimetro() . PHP_EOL;

$retangulo = new Retangulo(3.0, 4.0);
echo 'Área do retângulo: ' . $retangulo->calcularArea() . PHP_EOL;
echo 'Perímetro do retângulo: ' . $retangulo->calcularPerimetro() . PHP_EOL;

?>

✓ Resposta: Se uma classe implementa uma interface sem fornecer a implementação de todos os métodos, o PHP lançará um erro fatal: "Fatal error: Class X contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Y::z)". A classe ou deve ser declarada abstrata, ou deve implementar todos os métodos da interface. Exemplo:
interface A { public function metodo(); }
class B implements A {}
// Fatal error: Class B contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (A::metodo)

✓ Resposta:
<?php

interface Imprimivel {
    public function imprimir(): string;
}

interface Exportavel {
    public function exportar(): array;
}

class Relatorio implements Imprimivel, Exportavel {
    private string $titulo;
    private string $conteudo;

    public function __construct(string $titulo, string $conteudo) {
        $this->titulo = $titulo;
        $this->conteudo = $conteudo;
    }

    public function imprimir(): string {
        return "Título: {$this->titulo}\nConteúdo: {$this->conteudo}";
    }

    public function exportar(): array {
        return [
            'titulo' => $this->titulo,
            'conteudo' => $this->conteudo,
        ];
    }
}

$rel = new Relatorio('Relatório Anual', 'Desempenho da empresa em 2024.');
echo $rel->imprimir() . PHP_EOL;
print_r($rel->exportar());

?>

✓ Resposta:
<?php

interface EmitirSom {
    public function emitirSom(): string;
}

class Cachorro implements EmitirSom {
    public function emitirSom(): string {
        return "Au au";
    }
}

class Gato implements EmitirSom {
    public function emitirSom(): string {
        return "Miau";
    }
}

function processarEmitirSom(EmitirSom $objeto) {
    echo $objeto->emitirSom() . PHP_EOL;
}

processarEmitirSom(new Cachorro());
processarEmitirSom(new Gato());

?>

✓ Resposta: A classe C está incorreta porque implementa a interface B, que estende a interface A, e portanto herda o método foo(). A classe C deve implementar tanto o método foo() quanto o método bar(). Para corrigir, adicione a implementação de foo():
interface A { public function foo(); }
interface B extends A { public function bar(); }
class C implements B {
    public function foo() {
        // implementação
    }
    public function bar() {
        // implementação
    }
}

Referências