A herança é um dos pilares da programação orientada a objetos (POO) e permite que uma classe filha herde propriedades e métodos de uma classe pai. Em PHP, a herança é implementada com a palavra-chave extends. Isso promove reutilização de código e estabelece uma relação hierárquica entre classes.

Nesta aula, exploraremos como usar extends, a referência parent para acessar membros da classe pai, a sobrescrita de métodos (override) e boas práticas para decidir quando utilizar herança.

extends

A sintaxe para declarar herança em PHP é simples: após o nome da classe filha, usa-se a palavra-chave extends seguida do nome da classe pai. A classe filha herda todos os métodos e propriedades públicas e protegidas da classe pai (mas não os privados).

Exemplo:

<?php
class Animal {
    public $nome;

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

    public function fazerSom() {
        return "Som genérico";
    }
}

class Cachorro extends Animal {
    public function latir() {
        return "Au au!";
    }
}

$dog = new Cachorro("Rex");
echo $dog->nome; // Rex
echo $dog->fazerSom(); // Som genérico
echo $dog->latir(); // Au au!
?>

Neste exemplo, Cachorro herda a propriedade $nome e o método fazerSom() de Animal. Observe que não precisamos redefinir o construtor; ele também é herdado.

parent

A palavra-chave parent é usada para referenciar a classe pai de dentro da classe filha. É especialmente útil quando a classe filha sobrescreve um método, mas ainda deseja chamar a versão da classe pai.

Exemplo:

<?php
class Animal {
    public function fazerSom() {
        return "Som de animal";
    }
}

class Cachorro extends Animal {
    public function fazerSom() {
        return parent::fazerSom() . " e late";
    }
}

$dog = new Cachorro();
echo $dog->fazerSom(); // Som de animal e late
?>

Também podemos usar parent no construtor para reaproveitar a lógica de inicialização da classe pai:

<?php
class Animal {
    public $nome;

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

class Cachorro extends Animal {
    public $raca;

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

$dog = new Cachorro("Rex", "Labrador");
echo $dog->nome; // Rex
echo $dog->raca; // Labrador
?>

Sobrescrita

A sobrescrita (override) ocorre quando a classe filha redefine um método ou propriedade herdada da classe pai. O método na classe filha deve ter a mesma assinatura (nome e parâmetros) que o método na classe pai. O PHP permite alterar a visibilidade para torná-la mais permissiva (por exemplo, de protected para public), mas não mais restritiva.

Exemplo:

<?php
class Animal {
    protected function fazerSom() {
        return "Som genérico";
    }
}

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

$dog = new Cachorro();
echo $dog->fazerSom(); // Au au!
?>

Note que a visibilidade do método em Cachorro foi alterada de protected para public, o que é permitido. Se tentássemos o contrário (de public para protected), ocorreria um erro fatal.

Para evitar sobrescrita acidental, podemos usar a palavra-chave final no método da classe pai. Métodos final não podem ser sobrescritos.

<?php
class Animal {
    final public function fazerSom() {
        return "Som fixo";
    }
}

class Cachorro extends Animal {
    // Erro: Cannot override final method
    public function fazerSom() {
        return "Au au!";
    }
}
?>

Quando usar herança

Herança deve ser usada quando existe uma relação clara do tipo "é um" (is-a). Por exemplo, um cachorro é um animal, um carro é um veículo. Evite usar herança apenas para reutilizar código, pois isso pode levar a hierarquias profundas e frágeis. Prefira composição quando a relação for "tem um" (has-a).

Boas práticas:

  • Mantenha a hierarquia rasa (poucos níveis).
  • Use classes abstratas para definir contratos comuns.
  • Evite herdar de classes concretas que não foram projetadas para herança.
  • Considere usar interfaces ou traits quando a herança não for a melhor opção.

Exemplo de quando usar herança: uma classe Funcionario e Gerente que estende Funcionario. Um gerente é um funcionário com atributos adicionais.

<?php
class Funcionario {
    protected $nome;
    protected $salario;

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

    public function getSalario() {
        return $this->salario;
    }
}

class Gerente extends Funcionario {
    private $bonus;

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

    public function getSalario() {
        return parent::getSalario() + $this->bonus;
    }
}

$gerente = new Gerente("João", 5000, 1000);
echo $gerente->getSalario(); // 6000
?>

Boas práticas e observações finais

Sempre que sobrescrever um método, considere se precisa chamar o método pai usando parent::. Isso evita duplicação de lógica e mantém a consistência. Lembre-se de que o PHP suporta herança simples (uma classe só pode estender uma classe pai), mas você pode implementar múltiplas interfaces. Use final em classes ou métodos que não devem ser estendidos ou sobrescritos.

Referências

Exercícios

  1. Crie uma classe base Veiculo com propriedades marca e modelo, e um método exibirDetalhes() que retorna uma string com essas informações. Em seguida, crie uma classe Carro que estende Veiculo e adiciona a propriedade portas. Sobrescreva o método exibirDetalhes() para incluir o número de portas.
  2. ✓ Resposta:
    <?php
    class Veiculo {
        protected $marca;
        protected $modelo;
    
        public function __construct($marca, $modelo) {
            $this->marca = $marca;
            $this->modelo = $modelo;
        }
    
        public function exibirDetalhes() {
            return "Marca: $this->marca, Modelo: $this->modelo";
        }
    }
    
    class Carro extends Veiculo {
        private $portas;
    
        public function __construct($marca, $modelo, $portas) {
            parent::__construct($marca, $modelo);
            $this->portas = $portas;
        }
    
        public function exibirDetalhes() {
            return parent::exibirDetalhes() . ", Portas: $this->portas";
        }
    }
    
    $carro = new Carro("Toyota", "Corolla", 4);
    echo $carro->exibirDetalhes();
    ?>
  3. Explique por que o código abaixo gera um erro e como corrigi-lo:
  4. <?php
    class A {
        private function metodo() { }
    }
    
    class B extends A {
        public function metodo() { }
    }
    ?>

    ✓ Resposta: O erro ocorre porque métodos privados não são herdados, então a classe B não está sobrescrevendo o método, mas definindo um novo. Ao tentar sobrescrever um método privado, o PHP permite, mas não há relação de herança. O código não gera erro fatal, mas a chamada via polimorfismo não funcionará como esperado. Para corrigir, torne o método protected ou public na classe A se desejar que ele seja herdado e sobrescrito.
  5. Crie uma classe abstrata Forma com um método abstrato calcularArea(). Depois crie duas classes concretas Quadrado e Circulo que estendem Forma e implementam o método.
  6. ✓ Resposta:
    <?php
    abstract class Forma {
        abstract public function calcularArea();
    }
    
    class Quadrado extends Forma {
        private $lado;
    
        public function __construct($lado) {
            $this->lado = $lado;
        }
    
        public function calcularArea() {
            return $this->lado * $this->lado;
        }
    }
    
    class Circulo extends Forma {
        private $raio;
    
        public function __construct($raio) {
            $this->raio = $raio;
        }
    
        public function calcularArea() {
            return pi() * $this->raio * $this->raio;
        }
    }
    
    $quadrado = new Quadrado(4);
    echo $quadrado->calcularArea(); // 16
    $circulo = new Circulo(3);
    echo $circulo->calcularArea(); // 28.274333882308
    ?>
  7. Qual a diferença entre usar parent:: e $this-> ao chamar um método sobrescrito? Dê um exemplo.
  8. ✓ Resposta: parent:: chama o método da classe pai diretamente, ignorando a sobrescrita na classe filha. $this-> chama o método da instância atual, que pode ser o sobrescrito. Exemplo: parent::fazerSom() chama o método da classe pai, enquanto $this->fazerSom() chama o método da classe filha (se sobrescrito).
  9. Refatore o código abaixo para usar herança corretamente, evitando duplicação. Crie uma classe base Animal e duas classes filhas Gato e Peixe.
  10. <?php
    class Gato {
        public $nome;
        public function __construct($nome) { $this->nome = $nome; }
        public function mover() { return "Anda"; }
    }
    
    class Peixe {
        public $nome;
        public function __construct($nome) { $this->nome = $nome; }
        public function mover() { return "Nada"; }
    }
    ?>

    ✓ Resposta:
    <?php
    class Animal {
        protected $nome;
    
        public function __construct($nome) {
            $this->nome = $nome;
        }
    
        public function mover() {
            return "Se move";
        }
    }
    
    class Gato extends Animal {
        public function mover() {
            return "Anda";
        }
    }
    
    class Peixe extends Animal {
        public function mover() {
            return "Nada";
        }
    }
    
    $gato = new Gato("Felix");
    echo $gato->mover(); // Anda
    $peixe = new Peixe("Nemo");
    echo $peixe->mover(); // Nada
    ?>