Curso de PHP

Herencia de objetos en PHP: extends, parent y sobrescritura

Por Víctor Peña · Publicado el

Herencia de objetos en PHP

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos la herencia, el segundo pilar de la programación orientada a objetos que pusimos en práctica en la lección anterior.

¡Empecemos!

Qué es la herencia

La herencia permite crear una clase a partir de otra, heredando sus propiedades y métodos. La clase original se llama padre o clase base; la nueva, hija o derivada.

La idea es evitar repetir código. Si Perro y Gato comparten casi todo lo que hace Mascota, no tiene sentido escribir dos veces lo mismo.

La palabra extends

<?php
class Mascota
{
    public function __construct(
        public string $nombre,
        public int $edad,
    ) {}

    public function describir(): string
    {
        return "{$this->nombre}, {$this->edad} años";
    }
}

class Perro extends Mascota
{
    public function ladrar(): string
    {
        return "¡Guau!";
    }
}

$nupi = new Perro("Nupi", 3);

echo $nupi->describir();   // Nupi, 3 años  ← heredado
echo $nupi->ladrar();      // ¡Guau!        ← propio

Perro tiene todo lo de Mascota más lo suyo, sin haber copiado una sola línea.

Sobrescribir métodos

Una clase hija puede redefinir un método del padre para comportarse de otra forma:

<?php
class Gato extends Mascota
{
    public function describir(): string
    {
        return "El gato {$this->nombre} tiene {$this->edad} años";
    }
}

$michi = new Gato("Michi", 2);
echo $michi->describir();   // El gato Michi tiene 2 años

PHP usa siempre la versión más cercana al objeto: si la hija lo define, se usa el de la hija.

Reutilizar el método del padre con parent

Muchas veces no quieres reemplazar el comportamiento, sino ampliarlo. Para eso está parent:::

<?php
class Gato extends Mascota
{
    public function describir(): string
    {
        return parent::describir() . " y es un gato";
    }
}

echo (new Gato("Michi", 2))->describir();   // Michi, 2 años y es un gato

parent::describir() ejecuta la versión del padre y tú añades lo tuyo.

El constructor en la herencia

Aquí está el punto que más problemas da. Si la hija define su propio constructor, el del padre no se ejecuta automáticamente. Tienes que llamarlo tú:

<?php
class Perro extends Mascota
{
    public function __construct(
        string $nombre,
        int $edad,
        public string $raza,
    ) {
        parent::__construct($nombre, $edad);
    }

    public function describir(): string
    {
        return parent::describir() . ", raza {$this->raza}";
    }
}

$nupi = new Perro("Nupi", 3, "Bulldog");
echo $nupi->describir();   // Nupi, 3 años, raza Bulldog

Si olvidas parent::__construct(), las propiedades del padre quedan sin inicializar y obtendrás un error al usarlas. Es de los fallos más frecuentes al empezar con herencia.

El modificador protected

Ya vimos public y private. La herencia introduce el tercero:

Modificador Desde la propia clase Desde una clase hija Desde fuera
public
protected No
private No No
<?php
class Mascota
{
    protected string $codigo = "MSC-000";
    private string $secreto = "no visible";

    public function __construct(public string $nombre) {}
}

class Perro extends Mascota
{
    public function verCodigo(): string
    {
        return $this->codigo;    // funciona: es protected
        // return $this->secreto; // Error: es private
    }
}

protected es lo que quieres cuando algo debe compartirse con las clases hijas pero no exponerse al exterior.

Comprobar la relación entre clases

<?php
$nupi = new Perro("Nupi", 3, "Bulldog");

var_dump($nupi instanceof Perro);     // true
var_dump($nupi instanceof Mascota);   // true, también es una Mascota

Esto es clave: un objeto de la clase hija también es de la clase padre. Es lo que permite tratar perros y gatos como mascotas, y es la base del polimorfismo que veremos en un par de lecciones.

Herencia en cadena

Una clase hija puede a su vez tener hijas:

<?php
class Animal { }
class Mascota extends Animal { }
class Perro extends Mascota { }

Perro hereda de Mascota, que hereda de Animal. Funciona, pero conviene no abusar: más de dos o tres niveles hace el código difícil de seguir, porque para entender una clase tienes que ir subiendo por toda la cadena.

PHP no admite herencia múltiple

Una clase solo puede extender una clase:

<?php
class Perro extends Mascota, Vendible { }   // Error: no se puede

Es una limitación deliberada, porque la herencia múltiple genera ambigüedades. Cuando necesites compartir comportamiento entre clases que no están emparentadas, la solución son los traits y las interfaces, que veremos más adelante.

Impedir la herencia con final

Si una clase no debe extenderse, o un método no debe sobrescribirse:

<?php
final class Configuracion { }        // nadie puede heredar de ella

class Pago
{
    final public function procesar(): void { }   // no se puede sobrescribir
}

Se usa cuando el comportamiento es crítico y no quieres que nadie lo altere por accidente.

Cuándo usar herencia y cuándo no

La herencia es potente pero se abusa de ella. La prueba para saber si corresponde es simple:

¿La clase hija es un tipo de la clase padre?

  • Un Perro es una Mascota. Herencia correcta.
  • Un Motor no es un Auto, forma parte de él. Aquí no toca herencia: toca que Auto tenga una propiedad de tipo Motor.

Eso segundo se llama composición, y en general se prefiere sobre la herencia. La regla que repiten los libros es «composición sobre herencia», y significa que antes de extender una clase te preguntes si no basta con que una contenga a la otra.

Un ejemplo práctico

<?php
declare(strict_types=1);

class Empleado
{
    public function __construct(
        protected string $nombre,
        protected float $salarioBase,
    ) {}

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

    public function describir(): string
    {
        return "{$this->nombre}: " . number_format($this->calcularSalario(), 2);
    }
}

class Vendedor extends Empleado
{
    public function __construct(
        string $nombre,
        float $salarioBase,
        private float $ventas,
        private float $comision = 0.05,
    ) {
        parent::__construct($nombre, $salarioBase);
    }

    public function calcularSalario(): float
    {
        return parent::calcularSalario() + ($this->ventas * $this->comision);
    }
}

$ana = new Empleado("Ana", 4000);
$luis = new Vendedor("Luis", 3000, 50000);

echo $ana->describir() . "\n";    // Ana: 4,000.00
echo $luis->describir() . "\n";   // Luis: 5,500.00

Fíjate en algo elegante: describir() está escrito una sola vez en el padre, pero llama a calcularSalario(), que cada clase resuelve a su manera. Eso ya es polimorfismo en acción.

Errores comunes

  • Olvidar parent::__construct() cuando la hija define constructor.
  • Intentar acceder a propiedades private del padre desde la hija. Usa protected.
  • Cadenas de herencia demasiado profundas.
  • Usar herencia donde correspondía composición. Aplica la prueba del «es un».

Para cerrar

La herencia evita repetir código y permite que las clases hijas especialicen el comportamiento del padre. Recuerda parent:: para reutilizar, protected para compartir con las hijas, y la pregunta del «es un» antes de decidir extender.

En la siguiente lección veremos las clases anónimas.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp