La herencia permite crear una clase a partir de otra y reutilizar o especializar su comportamiento.
Imagina que estás programando un videojuego. Tienes un Guerrero, un Mago y un Arquero.
Todos tienen cosas en común: tienen un nombre, tienen vida (HP) y pueden moverse.
La forma novata (y dolorosa):
Creas tres clases (Guerrero, Mago, Arquero) y copias y pegas el código de $nombre, $vida y mover() en las tres. Si mañana quieres cambiar cómo funciona el movimiento, tienes que editar tres archivos. Mal.
La forma POO (Herencia):
Creas una clase base llamada Personaje (el padre) con lo común. Luego haces que las otras clases (los hijos) hereden de ella.
La palabra clave extends
Usamos extends para decir “La clase B es una versión especializada de la clase A”.
// Clase Padre (Base)
class Personaje {
public function __construct(
public string $nombre,
protected int $vida = 100 // protected: los hijos pueden tocarla
) {}
public function mover(): void {
echo "{$this->nombre} se está moviendo.\n";
}
}
// Clase Hija
// "El Guerrero ES UN Personaje"
class Guerrero extends Personaje {
public function atacar(): void {
echo "¡Espadazo!\n";
}
}Al crear la instancia ocurre lo siguiente:
$guts = new Guerrero("Guts");
// ¡El Guerrero tiene el método mover() aunque no lo escribimos en él!
$guts->mover(); // Salida: Guts se está moviendo.
$guts->atacar(); // Salida: ¡Espadazo!El Guerrero ha heredado todo lo público y protegido de Personaje.
Sobreescritura de métodos (overriding)
A veces, el hijo no quiere hacer exactamente lo mismo que el padre. Quiere comportarse de forma distinta.
Imagina que el Mago no camina, sino que levita. Podemos reescribir el método mover en la clase hija.
class Mago extends Personaje {
// Sobreescritura: Definimos el método con el MISMO nombre
public function mover(): void {
echo "{$this->nombre} se teletransporta mágicamente ✨.\n";
}
}
$gandalf = new Mago("Gandalf");
$gandalf->mover(); // Salida: Gandalf se teletransporta mágicamente ✨.PHP es listo: si llamas a mover(), busca primero en la clase Hija. Si existe, usa ese. Si no, sube al Padre.
Accediendo al padre (parent::)
¿Y si no quieres reemplazar totalmente al padre, sino solo añadir algo extra?
Un caso clásico es el Constructor. El Guerrero necesita inicializar su $nombre (como cualquier personaje) pero también su $arma (que es exclusiva de él).
Usamos parent:: para llamar a métodos de la clase superior.
class Guerrero extends Personaje {
public function __construct(
string $nombre,
public string $arma // Propiedad nueva
) {
// Llamamos al constructor del padre para que configure el nombre y la vida
parent::__construct($nombre);
}
public function atacar(): void {
echo "Ataca con {$this->arma}\n";
}
}Si sobreescribes el __construct en el hijo, PHP no llamará automáticamente al del padre. Es tu responsabilidad llamar a parent::__construct(...) si lo necesitas.
La palabra clave final (aquí se acaba la fiesta)
A veces creas una clase tan perfecta o crítica que no quieres que nadie la modifique ni herede de ella. Para prohibir la herencia, usamos final.
Clases finales
Nadie puede hacer extends de esto.
final class SistemaSeguridad {
// ...
}
// class Hack extends SistemaSeguridad {} // ❌ ERROR FATALMarcar una clase como final es razonable cuando no forma parte de un contrato de extensión. Evita herencias accidentales, aunque no tiene por qué ser la opción correcta para todas las clases.
Métodos finales
Permites heredar la clase, pero prohíbes cambiar un método específico.
class Personaje {
// Los hijos pueden cambiar mover(), pero NUNCA morir()
final public function morir(): void {
echo "Game Over.";
}
}