La herencia en la programación orientada a objetos (POO) es el mecanismo que permite que una clase reciba las propiedades y métodos de otra clase, y añada o modifique lo que necesite. La clase de la que se hereda se llama clase padre o superclase, y la que hereda es la clase hija o subclase.
En PHP la herencia se declara con la palabra reservada extends. Una clase hija puede usar todo lo que la clase padre haya definido como public o protected, y puede sustituir cualquier método por su propia versión.
La idea de fondo es no repetir código. Si tienes una clase Vehicle con propiedades como brand y model, no necesitas volver a escribirlas en Car y en Motorcycle: las dos heredan de Vehicle y se limitan a añadir lo que las diferencia.
Conviene aclarar desde el principio que PHP tiene herencia simple: una clase solo puede extender de otra clase. No existe la herencia múltiple. Cuando necesitas compartir comportamiento entre clases que no están en la misma jerarquía, la herramienta correcta son los traits o las interfaces, no la herencia.
Ejemplo de herencia en PHP
Partimos de una clase padre con lo que comparten todos los vehículos:
<?php
class Vehicle
{
public function __construct(
protected string $brand,
protected string $model,
protected int $wheels = 4
) {
}
public function start(): string
{
return "El {$this->brand} {$this->model} está arrancando";
}
public function getStatus(): string
{
return "{$this->brand} {$this->model} — {$this->wheels} ruedas";
}
}Ahora una clase hija que extiende de ella:
<?php
class Car extends Vehicle
{
public function __construct(
string $brand,
string $model,
private int $doors = 5
) {
parent::__construct($brand, $model, wheels: 4);
}
public function getDoors(): int
{
return $this->doors;
}
}
$car = new Car('Seat', 'León');
echo $car->start(); // El Seat León está arrancando
echo $car->getStatus(); // Seat León — 4 ruedas
echo $car->getDoors(); // 5Car no define start() ni getStatus() y sin embargo puede usarlos: los ha heredado. Fíjate en dos detalles importantes.
El primero es parent::__construct(). Cuando una clase hija define su propio constructor, el constructor del padre no se ejecuta automáticamente. Si no lo llamas explícitamente, $brand y $model se quedarán sin inicializar. Esto vale para cualquier método, no solo el constructor: parent::nombreDelMetodo() te da acceso a la versión del padre desde la hija.
El segundo es protected. Si las propiedades de Vehicle fueran private, la clase Car no podría acceder a ellas. protected es precisamente el nivel de visibilidad que dice «visible para mí y para quien herede de mí, pero no desde fuera». Es la pieza que conecta la herencia con la encapsulación.
Sobrescribir métodos del padre
Una clase hija puede sustituir cualquier método heredado por su propia versión. A esto se le llama sobrescritura, y es la base del polimorfismo:
<?php
class Motorcycle extends Vehicle
{
public function __construct(string $brand, string $model)
{
parent::__construct($brand, $model, wheels: 2);
}
public function start(): string
{
return "La {$this->brand} {$this->model} arranca con el pedal";
}
}
$moto = new Motorcycle('Yamaha', 'MT-07');
echo $moto->start(); // La Yamaha MT-07 arranca con el pedal
echo $moto->getStatus(); // Yamaha MT-07 — 2 ruedasstart() es la versión de Motorcycle; getStatus() sigue siendo la del padre. Cada clase hija decide qué reutiliza y qué reescribe.
Si quieres impedir que una clase hija sobrescriba un método concreto, márcalo como final:
php
public final function getStatus(): string
{
// Ninguna clase hija puede redefinir esto
}Y si quieres impedir que una clase se extienda en absoluto, final class Motorcycle. En un proyecto real esto se usa más de lo que parece: marcar como final por defecto y abrir la herencia solo donde tiene sentido evita jerarquías que crecen sin control.
Herencia y clases abstractas
La herencia se combina muy a menudo con las clases abstractas. Una clase abstracta no se puede instanciar: existe únicamente para ser heredada, y puede obligar a sus hijas a implementar ciertos métodos.
<?php
abstract class Vehicle
{
abstract public function start(): string;
public function getStatus(): string
{
return static::class . ' listo';
}
}Con esto, cualquier clase que extienda Vehicle está obligada a implementar start(), o PHP lanzará un error fatal. Es la forma de definir un contrato dentro de una jerarquía de herencia. Lo tienes desarrollado en el artículo sobre abstracción en POO.
Cuándo NO usar herencia
La herencia es la relación más rígida que puedes establecer entre dos clases, y es la que más se abusa. Antes de escribir extends, hazte una pregunta: ¿la clase hija es un tipo de la clase padre? Un Car es un Vehicle, así que la herencia encaja. Un Order no es un Database, aunque necesite usarla.
Cuando la respuesta es «lo necesita» en lugar de «lo es», lo correcto es la composición: pasar el objeto como dependencia en lugar de heredar de él.
// Mal: Order no es un tipo de Logger
class Order extends Logger { }
// Bien: Order usa un Logger
class Order
{
public function __construct(private Logger $logger) { }
}En Laravel esto se ve constantemente. Tus modelos extienden de Model porque un User es un modelo de Eloquent. Pero el servicio que envía correos no hereda de Mailer: lo recibe inyectado por el contenedor.
La otra señal de alarma es la profundidad. Una jerarquía de cinco niveles donde cada clase sobrescribe algo del nivel anterior es prácticamente imposible de depurar: para saber qué hace un método tienes que leer las cinco clases. Dos o tres niveles como máximo, y si necesitas más, casi siempre lo que buscabas era una interfaz.
Conclusiones
La herencia permite que una clase reciba las propiedades y métodos de otra con extends, reutilizando código y estableciendo una jerarquía. La clase hija puede añadir comportamiento propio, sobrescribir el del padre y acceder a él con parent::.
En PHP la herencia es simple: una clase solo extiende de otra. Para compartir comportamiento entre jerarquías distintas están los traits y las interfaces. Y antes de heredar, comprueba que la relación sea realmente «es un»: si es «lo usa», la composición te dará un código mucho más fácil de mantener.
Preguntas frecuentes
¿Cuál es la diferencia entre herencia y composición? Con herencia la clase hija es un tipo de la clase padre y recibe su comportamiento automáticamente. Con composición la clase contiene otro objeto y le delega trabajo. La composición es más flexible porque puedes cambiar la dependencia sin tocar la jerarquía.
¿Se puede heredar de varias clases en PHP? No. PHP tiene herencia simple: extends acepta una única clase. Para reutilizar código entre clases sin relación jerárquica se usan traits, y para definir contratos compartidos, interfaces (una clase sí puede implementar varias).
¿Qué diferencia hay entre private y protected en la herencia? Una propiedad o método private solo es accesible dentro de la clase que lo declara; la clase hija no lo ve. protected es accesible dentro de la clase y en todas las que hereden de ella, pero no desde fuera.
¿Es obligatorio llamar a parent::__construct()? No es obligatorio, pero si la clase hija define su propio constructor y no lo llama, el del padre no se ejecuta y las propiedades que inicializaba quedarán sin valor. Si la hija no define constructor, se usa el del padre directamente.
¿Qué son los cuatro pilares de la POO? Abstracción, encapsulación, herencia y polimorfismo. La abstracción modela lo esencial de una entidad, la encapsulación protege su estado interno, la herencia permite reutilizar y extender clases, y el polimorfismo permite que un mismo método se comporte de forma distinta según el objeto.
Si quieres dominar PHP 8 de principio a fin, con la POO aplicada a proyectos reales, tienes la Ruta de aprendizaje de PHP 8.