Interfaces em PHP
LidoNesta aula, você aprenderá sobre interfaces em PHP, uma ferramenta essencial para definir contratos entre classes. Exploraremos a sintaxe de declaração, a implementação com 'implements', a composição de múltiplas interfaces e o conceito de contratos no design de software. Ao final, você estará apto a usar interfaces para criar código mais flexível e desacoplado.
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, ouLoggerInterface. - 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
- Crie uma interface chamada
FormaGeometricacom métodoscalcularArea(): floatecalcularPerimetro(): float. Em seguida, implemente essa interface em duas classes:Circulo(que recebe o raio no construtor) eRetangulo(que recebe base e altura). Teste as classes criando objetos e exibindo área e perímetro. - 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.
- Crie duas interfaces:
Imprimivelcom métodoimprimir(): stringeExportavelcom métodoexportar(): array. Implemente ambas em uma classeRelatorioque tenha propriedadestituloeconteudo. O métodoimprimir()deve retornar o relatório em formato textual, eexportar()deve retornar um array com as propriedades. - 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étodoemitirSom()e imprime o resultado. Use com objetos de diferentes classes que implementam a interface. - 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.
<?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;
?>
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)
<?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());
?>
<?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());
?>
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
}
}