Classes abstratas

1. Conceitos Fundamentais de Classes Abstratas

Uma classe abstrata em PHP é uma classe que não pode ser instanciada diretamente. Seu propósito principal é servir como modelo base para outras classes, definindo uma estrutura comum que deve ser seguida pelas classes filhas. Diferente de classes concretas — que podem ser instanciadas — e de interfaces — que apenas definem contratos de métodos —, as classes abstratas podem conter tanto métodos abstratos (apenas assinatura) quanto métodos concretos (com implementação completa).

A palavra-chave abstract é utilizada para declarar tanto classes quanto métodos abstratos. Uma regra fundamental é: se uma classe contém ao menos um método abstrato, ela deve ser declarada como abstrata. Além disso, métodos abstratos não podem ser private, pois precisam ser implementados por classes filhas.

2. Sintaxe e Declaração de Classes Abstratas

A declaração de uma classe abstrata segue a sintaxe:

<?php

abstract class FormaGeometrica {
    // Método abstrato: apenas assinatura
    abstract public function calcularArea(): float;

    // Método concreto: implementação completa
    public function descricao(): string {
        return "Esta é uma forma geométrica.";
    }
}

Note que métodos abstratos terminam com ponto e vírgula (;) e não possuem corpo ({}). Já métodos concretos podem conter lógica completa, inclusive acessar propriedades da classe.

3. Herança e Implementação de Classes Abstratas

Para utilizar uma classe abstrata, é necessário estendê-la com extends e implementar todos os seus métodos abstratos. O não cumprimento gera um Fatal error.

<?php

class Circulo extends FormaGeometrica {
    private float $raio;

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

    // Implementação obrigatória do método abstrato
    public function calcularArea(): float {
        return pi() * ($this->raio ** 2);
    }
}

$circulo = new Circulo(5.0);
echo $circulo->calcularArea(); // 78.5398...
echo $circulo->descricao();    // "Esta é uma forma geométrica."

Erro comum: esquecer de implementar um método abstrato resulta em:

Fatal error: Class Circulo contains 1 abstract method and must therefore be declared abstract or implement the remaining methods

4. Propriedades e Constantes em Classes Abstratas

Desde o PHP 8.0, é possível declarar propriedades abstratas, forçando classes filhas a defini-las:

<?php

abstract class Animal {
    // Propriedade abstrata (PHP 8.0+)
    public abstract string $nome;

    // Constante de classe
    public const REINO = "Animalia";

    // Método concreto
    public function apresentar(): string {
        return "Eu sou um {$this->nome}.";
    }
}

class Cachorro extends Animal {
    public string $nome = "Rex";
}

$dog = new Cachorro();
echo $dog->apresentar(); // "Eu sou um Rex."

Modificadores de acesso funcionam normalmente: public, protected e private. Propriedades abstratas não podem ser private, pois precisam ser visíveis para a classe filha.

5. Classes Abstratas vs Interfaces

A escolha entre classe abstrata e interface depende do cenário:

  • Classe abstrata: quando há comportamento compartilhado (métodos concretos) e estado (propriedades). Exemplo: classe Animal com método concreto respirar() e método abstrato emitirSom().
  • Interface: quando apenas um contrato de métodos é necessário, sem implementação. Exemplo: interface Movable com método mover().
<?php

abstract class Animal {
    abstract public function emitirSom(): string;

    public function respirar(): string {
        return "Respirando...";
    }
}

interface Movable {
    public function mover(): string;
}

class Passaro extends Animal implements Movable {
    public function emitirSom(): string {
        return "Piu piu!";
    }

    public function mover(): string {
        return "Voando...";
    }
}

Use classe abstrata quando houver código compartilhado; use interface para contratos puros.

6. Padrões de Projeto com Classes Abstratas

Template Method Pattern

Define o esqueleto de um algoritmo, deixando etapas específicas para subclasses:

<?php

abstract class Relatorio {
    // Template method
    public function gerar(): string {
        $cabecalho = $this->criarCabecalho();
        $corpo = $this->criarCorpo();
        $rodape = $this->criarRodape();
        return $cabecalho . "\n" . $corpo . "\n" . $rodape;
    }

    abstract protected function criarCabecalho(): string;
    abstract protected function criarCorpo(): string;
    abstract protected function criarRodape(): string;
}

class RelatorioPDF extends Relatorio {
    protected function criarCabecalho(): string {
        return "=== CABEÇALHO PDF ===";
    }

    protected function criarCorpo(): string {
        return "Conteúdo do relatório PDF";
    }

    protected function criarRodape(): string {
        return "=== RODAPÉ PDF ===";
    }
}

Factory Method Pattern

Cria objetos sem especificar a classe exata:

<?php

abstract class DatabaseConnection {
    abstract public function connect(): string;
    abstract public function query(string $sql): array;

    public static function create(string $driver): self {
        return match ($driver) {
            'mysql' => new MySQLConnection(),
            'pgsql' => new PostgreSQLConnection(),
            default => throw new InvalidArgumentException("Driver inválido"),
        };
    }
}

class MySQLConnection extends DatabaseConnection {
    public function connect(): string {
        return "Conectado ao MySQL";
    }

    public function query(string $sql): array {
        return ["resultado_mysql"];
    }
}

7. Boas Práticas e Cuidados

  • Evite hierarquias profundas: mais de 3 níveis de herança tornam o código difícil de manter. Prefira composição quando possível.
  • Composição sobre herança: se uma classe precisa apenas de comportamento, mas não de identidade, considere usar traits ou injetar dependências.
  • Uso de final: métodos final em classes abstratas impedem que subclasses os sobrescrevam, útil para garantir comportamento crítico:
<?php

abstract class BaseController {
    final public function executar(): void {
        $this->validar();
        $this->processar();
    }

    abstract protected function validar(): void;
    abstract protected function processar(): void;
}

8. Exemplo Completo e Cenários Avançados

Sistema de pagamentos com classe abstrata:

<?php

abstract class PaymentGateway {
    protected string $apiKey;
    protected array $config;

    public function __construct(string $apiKey, array $config = []) {
        $this->apiKey = $apiKey;
        $this->config = $config;
        $this->inicializar();
    }

    abstract protected function inicializar(): void;
    abstract public function cobrar(float $valor, array $dados): string;
    abstract public function reembolsar(string $transacaoId): bool;

    // Método concreto compartilhado
    public function formatarMoeda(float $valor): string {
        return number_format($valor, 2, ',', '.');
    }
}

class PayPalGateway extends PaymentGateway {
    protected function inicializar(): void {
        // Lógica específica do PayPal
    }

    public function cobrar(float $valor, array $dados): string {
        if ($valor <= 0) {
            throw new InvalidArgumentException("Valor inválido");
        }
        // Simulação de cobrança
        return "PAYPAL-" . uniqid();
    }

    public function reembolsar(string $transacaoId): bool {
        return str_starts_with($transacaoId, "PAYPAL-");
    }
}

class StripeGateway extends PaymentGateway {
    protected function inicializar(): void {
        // Configuração específica do Stripe
    }

    public function cobrar(float $valor, array $dados): string {
        if (!isset($dados['token'])) {
            throw new InvalidArgumentException("Token de pagamento ausente");
        }
        return "STRIPE-" . bin2hex(random_bytes(8));
    }

    public function reembolsar(string $transacaoId): bool {
        return str_starts_with($transacaoId, "STRIPE-");
    }
}

// Uso
try {
    $paypal = new PayPalGateway("api_key_123");
    $transacao = $paypal->cobrar(150.00, ['email' => 'cliente@email.com']);
    echo "Transação: $transacao\n";

    $stripe = new StripeGateway("sk_test_456");
    $transacao2 = $stripe->cobrar(200.00, ['token' => 'tok_visa']);
    echo "Transação: $transacao2\n";
} catch (Exception $e) {
    echo "Erro: " . $e->getMessage();
}

Este exemplo demonstra tratamento de exceções, validação em métodos abstratos e uso de métodos concretos compartilhados.

Referências