Herança e Sobrescrita de Métodos em PHP

1. Fundamentos da Herança em PHP

A herança é um dos pilares da programação orientada a objetos (POO) que permite que uma classe filha (child) herde propriedades e métodos de uma classe pai (parent). Em PHP, a herança é implementada com a palavra-chave extends e suporta apenas herança simples — uma classe pode herdar de apenas uma classe pai.

Sintaxe básica:

<?php

class Animal {
    public $nome;

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

    public function mover() {
        return "{$this->nome} está se movendo.";
    }
}

class Cachorro extends Animal {
    public function latir() {
        return "{$this->nome} está latindo: Au au!";
    }
}

$dog = new Cachorro("Rex");
echo $dog->mover();  // Rex está se movendo.
echo $dog->latir();  // Rex está latindo: Au au!

A classe Cachorro herda automaticamente o construtor, a propriedade $nome e o método mover() da classe Animal, demonstrando o reuso de código.

2. Acessando Propriedades e Métodos Herdados

Para acessar explicitamente métodos ou propriedades da classe pai dentro da classe filha, usamos o operador parent::. Isso é especialmente útil quando queremos estender o comportamento herdado.

Modificadores de acesso e herança:

Modificador Herdado? Acessível na filha? Acessível externamente?
public Sim Sim Sim
protected Sim Sim Não
private Não Não Não

Exemplo com construtor herdado:

<?php

class Veiculo {
    protected $marca;
    protected $modelo;

    public function __construct($marca, $modelo) {
        $this->marca = $marca;
        $this->modelo = $modelo;
    }

    public function getInfo() {
        return "{$this->marca} {$this->modelo}";
    }
}

class Carro extends Veiculo {
    private $portas;

    public function __construct($marca, $modelo, $portas) {
        parent::__construct($marca, $modelo);
        $this->portas = $portas;
    }

    public function getInfo() {
        return parent::getInfo() . " - {$this->portas} portas";
    }
}

$carro = new Carro("Honda", "Civic", 4);
echo $carro->getInfo(); // Honda Civic - 4 portas

3. Sobrescrita de Métodos (Overriding)

A sobrescrita de métodos permite que uma classe filha redefina um método herdado da classe pai. Em PHP, para sobrescrever um método, a classe filha deve declarar um método com o mesmo nome. A assinatura deve ser compatível — o número de parâmetros obrigatórios deve ser o mesmo, embora PHP seja flexível com tipos em versões anteriores ao PHP 8.

Regras importantes:
- O método na classe filha deve ter a mesma visibilidade ou menos restritiva (não pode tornar um método public em private)
- A partir do PHP 8.1, tipos de parâmetros e retorno devem ser compatíveis (covariância/contravariância)

Exemplo prático:

<?php

class Animal {
    public function falar() {
        return "O animal faz um som.";
    }
}

class Gato extends Animal {
    public function falar() {
        return "Miau!";
    }
}

class Vaca extends Animal {
    public function falar() {
        return "Muuu!";
    }
}

$animais = [new Animal(), new Gato(), new Vaca()];
foreach ($animais as $animal) {
    echo $animal->falar() . "\n";
}
// O animal faz um som.
// Miau!
// Muuu!

4. A Palavra-chave final e Herança

A palavra-chave final impede que métodos sejam sobrescritos ou que classes sejam estendidas.

Métodos final:

<?php

class Pagamento {
    final public function processar() {
        return "Processando pagamento...";
    }
}

class PagamentoCartao extends Pagamento {
    // Erro! Não pode sobrescrever método final
    // public function processar() { ... }
}

Classes final:

<?php

final class Configuracao {
    public static function getVersao() {
        return "1.0.0";
    }
}

// Erro! Não pode estender classe final
// class ConfiguracaoAvancada extends Configuracao { ... }

Quando usar final:
- Para garantir que um método crítico não seja alterado (ex: validação de segurança)
- Em classes de configuração ou utilitárias que não devem ser modificadas
- Para design explícito: documentar que a classe não foi projetada para ser estendida

5. Herança e Construtores

A sobrescrita de construtores segue as mesmas regras, mas com uma prática recomendada: sempre chamar o construtor da classe pai quando necessário.

Ordem de execução:

<?php

class Base {
    public function __construct() {
        echo "Construtor da Base\n";
    }
}

class Derivada extends Base {
    public function __construct() {
        parent::__construct(); // Chama construtor da Base primeiro
        echo "Construtor da Derivada\n";
    }
}

$obj = new Derivada();
// Construtor da Base
// Construtor da Derivada

Boas práticas:
- Se a classe pai tem um construtor com parâmetros obrigatórios, a filha deve chamá-lo explicitamente
- Use parent::__construct() no início do construtor da filha para garantir inicialização adequada
- Evite lógica complexa em construtores; prefira métodos de inicialização separados

6. Herança vs. Composição

Embora a herança seja poderosa, ela tem limitações. PHP suporta apenas herança simples, e hierarquias profundas podem tornar o código frágil e difícil de manter.

Problemas com herança excessiva:
- Acoplamento forte entre classes
- Dificuldade de testar classes isoladamente
- Herança de comportamentos indesejados

Composição como alternativa:

<?php

interface ComportamentoVoo {
    public function voar();
}

class Asa implements ComportamentoVoo {
    public function voar() {
        return "Voando com asas!";
    }
}

class Foguete implements ComportamentoVoo {
    public function voar() {
        return "Voando com foguete!";
    }
}

class Ave {
    private $comportamentoVoo;

    public function __construct(ComportamentoVoo $comportamento) {
        $this->comportamentoVoo = $comportamento;
    }

    public function voar() {
        return $this->comportamentoVoo->voar();
    }
}

$ave = new Ave(new Asa());
echo $ave->voar(); // Voando com asas!

A composição oferece maior flexibilidade, permitindo trocar comportamentos em tempo de execução.

7. Exemplo Prático Completo

Vamos construir um sistema de funcionários que demonstra herança, sobrescrita e o uso de final.

<?php

class Funcionario {
    protected $nome;
    protected $salarioBase;

    public function __construct($nome, $salarioBase) {
        $this->nome = $nome;
        $this->salarioBase = $salarioBase;
    }

    public function calcularSalario() {
        return $this->salarioBase;
    }

    public function getCargo() {
        return "Funcionário";
    }

    final public function getNome() {
        return $this->nome;
    }
}

class Gerente extends Funcionario {
    private $bonificacao;

    public function __construct($nome, $salarioBase, $bonificacao) {
        parent::__construct($nome, $salarioBase);
        $this->bonificacao = $bonificacao;
    }

    public function calcularSalario() {
        return parent::calcularSalario() + $this->bonificacao;
    }

    public function getCargo() {
        return "Gerente";
    }
}

class Diretor extends Gerente {
    private $participacaoLucros;

    public function __construct($nome, $salarioBase, $bonificacao, $participacaoLucros) {
        parent::__construct($nome, $salarioBase, $bonificacao);
        $this->participacaoLucros = $participacaoLucros;
    }

    public function calcularSalario() {
        return parent::calcularSalario() + $this->participacaoLucros;
    }

    public function getCargo() {
        return "Diretor";
    }
}

// Uso do sistema
$funcionario = new Funcionario("João", 3000);
$gerente = new Gerente("Maria", 5000, 2000);
$diretor = new Diretor("Carlos", 8000, 3000, 5000);

$funcionarios = [$funcionario, $gerente, $diretor];
foreach ($funcionarios as $f) {
    echo "{$f->getNome()} - {$f->getCargo()}: R$ {$f->calcularSalario()}\n";
}
// João - Funcionário: R$ 3000
// Maria - Gerente: R$ 7000
// Carlos - Diretor: R$ 13000

Neste exemplo:
- getNome() é final, impedindo que seja sobrescrito
- calcularSalario() é sobrescrito em cada nível, chamando parent:: para reutilizar a lógica da classe pai
- A hierarquia demonstra herança em três níveis com construtores encadeados

Referências