Classes abstratas são um conceito fundamental da programação orientada a objetos (POO) que permite definir uma estrutura base para outras classes, sem que ela mesma possa ser instanciada. Em PHP, uma classe abstrata funciona como um modelo: ela pode conter implementações completas de métodos (comportamentos comuns) e também declarar métodos abstratos, que são obrigatoriamente implementados pelas classes filhas. Isso garante que todas as subclasses sigam um contrato mínimo, ao mesmo tempo que compartilham código repetido.

Nesta aula, vamos aprender a sintaxe e as regras das classes abstratas, entender a diferença entre métodos concretos e abstratos, e ver exemplos práticos de como aplicá-las para projetar sistemas mais flexíveis e bem estruturados. Você também vai conhecer boas práticas e situações onde o uso de classes abstratas é mais adequado do que interfaces ou classes comuns.

abstract

A palavra-chave abstract é usada em PHP para declarar uma classe como abstrata. Uma classe abstrata não pode ser instanciada diretamente; ela serve exclusivamente como classe base para outras classes. Para declarar uma classe abstrata, basta colocar a palavra abstract antes da palavra class:

abstract class Animal {
    // código da classe
}

Qualquer tentativa de instanciar uma classe abstrata resultará em um erro fatal:

// $animal = new Animal(); // Erro: Cannot instantiate abstract class

Além disso, uma classe abstrata pode conter métodos abstratos (sem implementação) e métodos concretos (com implementação). A única restrição é que, se uma classe contém pelo menos um método abstrato, ela deve ser declarada como abstrata.

Métodos abstratos

Métodos abstratos são declarados sem corpo, apenas com a assinatura (nome e parâmetros), e são seguidos por ponto e vírgula. Eles obrigam qualquer classe concreta (não abstrata) que estenda a classe abstrata a implementar esses métodos. A sintaxe é:

abstract class Animal {
    abstract public function fazerSom(): string;
    abstract protected function mover(): void;
}

Métodos abstratos não podem ser privados (private), pois precisam ser acessíveis pelas subclasses. Eles podem ser public ou protected. Além disso, a assinatura do método na subclasse deve ser compatível: mesmo nome, mesmo número de parâmetros e tipo de retorno (se declarado). O nível de visibilidade pode ser igual ou mais permissivo (ex.: protected na classe abstrata pode se tornar public na subclasse).

Exemplo de implementação:

class Cachorro extends Animal {
    public function fazerSom(): string {
        return "Au au";
    }

    protected function mover(): void {
        echo "O cachorro está correndo.";
    }
}

Quando usar

Classes abstratas são ideais quando você tem uma hierarquia de classes que compartilham comportamento comum, mas também precisam garantir que cada subclasse implemente certas funcionalidades específicas. Alguns cenários comuns:

  • Modelagem de entidades do mundo real: Por exemplo, uma classe abstrata Veiculo pode ter métodos concretos como acelerar() e frear(), mas deixar métodos abstratos como ligarMotor() para cada tipo de veículo (carro, moto, caminhão).
  • Frameworks e bibliotecas: Muitos frameworks PHP (como Laravel, Symfony) usam classes abstratas para fornecer uma base que os desenvolvedores estendem, implementando apenas os métodos necessários.
  • Template Method Pattern: Uma classe abstrata define o esqueleto de um algoritmo (método concreto) e delega partes variáveis para métodos abstratos que as subclasses implementam.

Compare com interfaces: interfaces definem apenas contratos (assinaturas de métodos), sem implementação. Classes abstratas podem ter implementação parcial. Use interfaces quando você quer definir um contrato que pode ser implementado por classes não relacionadas; use classes abstratas quando há uma relação de herança com código compartilhado.

Exemplos

Vamos construir um exemplo completo: uma classe abstrata FormaGeometrica com um método concreto para exibir informações e métodos abstratos para calcular área e perímetro.

abstract class FormaGeometrica {
    protected string $cor;

    public function __construct(string $cor) {
        $this->cor = $cor;
    }

    abstract public function calcularArea(): float;
    abstract public function calcularPerimetro(): float;

    public function exibirInfo(): void {
        echo "Cor: {$this->cor}, Área: {$this->calcularArea()}, Perímetro: {$this->calcularPerimetro()}";
    }
}

class Circulo extends FormaGeometrica {
    private float $raio;

    public function __construct(string $cor, float $raio) {
        parent::__construct($cor);
        $this->raio = $raio;
    }

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

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

class Retangulo extends FormaGeometrica {
    private float $largura;
    private float $altura;

    public function __construct(string $cor, float $largura, float $altura) {
        parent::__construct($cor);
        $this->largura = $largura;
        $this->altura = $altura;
    }

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

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

// Uso
$circulo = new Circulo("Vermelho", 5);
$circulo->exibirInfo(); // Cor: Vermelho, Área: 78.539816339745, Perímetro: 31.415926535898

$retangulo = new Retangulo("Azul", 4, 7);
$retangulo->exibirInfo(); // Cor: Azul, Área: 28, Perímetro: 22

Note que não podemos instanciar FormaGeometrica diretamente. As subclasses Circulo e Retangulo são obrigadas a implementar os métodos abstratos, garantindo que qualquer forma geométrica tenha área e perímetro calculáveis.

Boas práticas

  • Mantenha classes abstratas coesas: elas devem representar um conceito claro e não acumular responsabilidades demais.
  • Evite métodos abstratos demais: se uma classe abstrata tem muitos métodos abstratos, talvez uma interface seja mais adequada.
  • Use classes abstratas para fornecer implementação padrão (métodos concretos) que as subclasses podem herdar ou sobrescrever.
  • Documente os métodos abstratos com PHPDoc para que quem for implementar saiba o que se espera.

Referências

Exercícios

  1. Crie uma classe abstrata chamada Funcionario com um método concreto getNome() e um método abstrato calcularSalario(). Em seguida, crie duas subclasses: FuncionarioHorista (que recebe por horas) e FuncionarioAssalariado (com salário fixo).

    ✓ Resposta:
    abstract class Funcionario {
        protected string $nome;
    
        public function __construct(string $nome) {
            $this->nome = $nome;
        }
    
        public function getNome(): string {
            return $this->nome;
        }
    
        abstract public function calcularSalario(): float;
    }
    
    class FuncionarioHorista extends Funcionario {
        private float $horasTrabalhadas;
        private float $valorHora;
    
        public function __construct(string $nome, float $horasTrabalhadas, float $valorHora) {
            parent::__construct($nome);
            $this->horasTrabalhadas = $horasTrabalhadas;
            $this->valorHora = $valorHora;
        }
    
        public function calcularSalario(): float {
            return $this->horasTrabalhadas * $this->valorHora;
        }
    }
    
    class FuncionarioAssalariado extends Funcionario {
        private float $salarioMensal;
    
        public function __construct(string $nome, float $salarioMensal) {
            parent::__construct($nome);
            $this->salarioMensal = $salarioMensal;
        }
    
        public function calcularSalario(): float {
            return $this->salarioMensal;
        }
    }
  2. Explique por que uma classe abstrata não pode ser instanciada diretamente e qual a diferença entre método abstrato e método concreto.

    ✓ Resposta:

    Uma classe abstrata não pode ser instanciada porque ela representa um conceito genérico que não tem implementação completa (possui métodos abstratos). A instância só faz sentido para classes concretas que implementam todos os métodos. Método abstrato é declarado sem corpo, apenas com assinatura, e deve ser implementado na subclasse. Método concreto possui implementação na própria classe abstrata e pode ser herdado ou sobrescrito.

  3. Dado o código: abstract class A { abstract public function foo(); } class B extends A { public function foo() { return 'B'; } }, é possível adicionar um método bar() concreto na classe A? Se sim, escreva um exemplo.

    ✓ Resposta:
    abstract class A {
        abstract public function foo(): string;
    
        public function bar(): string {
            return "Método concreto em A";
        }
    }
    
    class B extends A {
        public function foo(): string {
            return 'B';
        }
    }
    
    $b = new B();
    echo $b->bar(); // Funciona: "Método concreto em A"
  4. Crie uma classe abstrata Midia com propriedades titulo e ano, um método concreto getTitulo() e um método abstrato getTipo(). Depois crie classes Livro e Filme que a estendam.

    ✓ Resposta:
    abstract class Midia {
        protected string $titulo;
        protected int $ano;
    
        public function __construct(string $titulo, int $ano) {
            $this->titulo = $titulo;
            $this->ano = $ano;
        }
    
        public function getTitulo(): string {
            return $this->titulo;
        }
    
        abstract public function getTipo(): string;
    }
    
    class Livro extends Midia {
        private string $autor;
    
        public function __construct(string $titulo, int $ano, string $autor) {
            parent::__construct($titulo, $ano);
            $this->autor = $autor;
        }
    
        public function getTipo(): string {
            return "Livro";
        }
    
        public function getAutor(): string {
            return $this->autor;
        }
    }
    
    class Filme extends Midia {
        private string $diretor;
    
        public function __construct(string $titulo, int $ano, string $diretor) {
            parent::__construct($titulo, $ano);
            $this->diretor = $diretor;
        }
    
        public function getTipo(): string {
            return "Filme";
        }
    
        public function getDiretor(): string {
            return $this->diretor;
        }
    }
  5. Considere o padrão Template Method. Escreva uma classe abstrata Relatorio com um método concreto gerar() que chama métodos abstratos cabecalho(), conteudo() e rodape(). Implemente uma subclasse RelatorioHTML.

    ✓ Resposta:
    abstract class Relatorio {
        public function gerar(): void {
            echo $this->cabecalho();
            echo $this->conteudo();
            echo $this->rodape();
        }
    
        abstract protected function cabecalho(): string;
        abstract protected function conteudo(): string;
        abstract protected function rodape(): string;
    }
    
    class RelatorioHTML extends Relatorio {
        protected function cabecalho(): string {
            return "";
        }
    
        protected function conteudo(): string {
            return "

    Relatório

    Conteúdo do relatório...

    "; } protected function rodape(): string { return ""; } } $rel = new RelatorioHTML(); $rel->gerar();