Curso de PHP
Clases abstractas en PHP: qué son y cuándo usarlas
Por Víctor Peña · Publicado el

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos las clases abstractas. Ya las usamos de pasada en la lección de polimorfismo; ahora toca entenderlas a fondo.
¡Empecemos!
Qué es una clase abstracta
Una clase abstracta es una clase que no se puede instanciar: sirve únicamente como base para otras clases.
Piensa en Transportista. En el mundo real no existe «un transportista» genérico: existe un envío local, uno nacional, uno internacional. La clase Transportista define lo que todos tienen en común, pero no tiene sentido crear un objeto de ella.
<?php
abstract class Transportista
{
// ...
}
$t = new Transportista(); // Error fatal: no se puede instanciar
Métodos abstractos
Un método abstracto declara la firma pero no el cuerpo. Obliga a que cada clase hija lo implemente:
<?php
abstract class Figura
{
abstract public function calcularArea(): float;
}
class Circulo extends Figura
{
public function __construct(private float $radio) {}
public function calcularArea(): float
{
return pi() * $this->radio ** 2;
}
}
class Rectangulo extends Figura
{
public function __construct(
private float $ancho,
private float $alto,
) {}
public function calcularArea(): float
{
return $this->ancho * $this->alto;
}
}
Fíjate en que el método abstracto termina en punto y coma, sin llaves. No hay nada que ejecutar: solo dice «toda figura debe saber calcular su área».
Si una clase hija olvida implementarlo, PHP falla al compilar. Eso es una garantía, no una molestia: el error aparece de inmediato y no cuando alguien llame al método en producción.
Lo que las hace útiles: mezclar abstracto y concreto
Aquí está la diferencia real con una interfaz. Una clase abstracta puede tener también métodos con implementación y propiedades:
<?php
declare(strict_types=1);
abstract class Figura
{
public function __construct(
protected string $nombre,
) {}
// Cada hija lo resuelve a su manera
abstract public function calcularArea(): float;
// Esto lo heredan todas, ya resuelto
public function describir(): string
{
return sprintf(
"%s con área de %.2f",
$this->nombre,
$this->calcularArea()
);
}
}
class Circulo extends Figura
{
public function __construct(private float $radio)
{
parent::__construct("Círculo");
}
public function calcularArea(): float
{
return pi() * $this->radio ** 2;
}
}
echo (new Circulo(5))->describir(); // Círculo con área de 78.54
describir() se escribe una sola vez y funciona para todas las figuras presentes y futuras, aunque cada una calcule su área de forma distinta. Eso es lo que aporta una clase abstracta: código compartido más contrato obligatorio.
El patrón que más se usa: método plantilla
Es una aplicación directa de lo anterior y aparece constantemente en frameworks:
<?php
abstract class ProcesadorDeArchivo
{
// El esqueleto del proceso, fijo para todos
public function procesar(string $ruta): array
{
$this->validar($ruta);
$datos = $this->leer($ruta);
$limpios = $this->limpiar($datos);
return $limpios;
}
protected function validar(string $ruta): void
{
if (!file_exists($ruta)) {
throw new RuntimeException("El archivo no existe: $ruta");
}
}
protected function limpiar(array $datos): array
{
return array_filter($datos, fn($fila) => !empty($fila));
}
// Lo único que cambia según el formato
abstract protected function leer(string $ruta): array;
}
class ProcesadorCsv extends ProcesadorDeArchivo
{
protected function leer(string $ruta): array
{
return array_map('str_getcsv', file($ruta));
}
}
class ProcesadorJson extends ProcesadorDeArchivo
{
protected function leer(string $ruta): array
{
return json_decode(file_get_contents($ruta), true);
}
}
El orden de los pasos —validar, leer, limpiar— está fijado en la clase padre. Cada hija solo aporta la pieza que le corresponde. Añadir soporte para XML es escribir un método.
Reglas a tener en cuenta
- Una clase con al menos un método abstracto debe declararse
abstract. - Una clase abstracta puede no tener ningún método abstracto. Sigue sin poder instanciarse.
- Los métodos abstractos no pueden ser
private: la hija no podría implementarlos. Usapublicoprotected. - La hija debe respetar la misma firma: mismos tipos de parámetros y de retorno.
- Una clase abstracta puede tener constructor, y las hijas lo llaman con
parent::__construct().
Clase abstracta o interfaz
Esta es la duda clásica, y la respuesta es más simple de lo que parece.
| Clase abstracta | Interfaz | |
|---|---|---|
| Código compartido | Sí | No |
| Propiedades | Sí | No |
| Constructor | Sí | No |
| Cuántas se pueden usar | Solo una | Varias |
| Relación que expresa | «es un» | «puede hacer» |
La forma de decidir:
- Clase abstracta cuando las clases están emparentadas y comparten código. Un círculo es una figura.
- Interfaz cuando solo comparten una capacidad, sin parentesco. Una factura y un reporte pueden ser exportables, pero no tienen nada que ver entre sí.
Y no son excluyentes: lo habitual en código profesional es una clase abstracta que además implementa una o varias interfaces.
<?php
abstract class Figura implements Exportable, JsonSerializable
{
// ...
}
Un ejemplo práctico
Sistema de notificaciones:
<?php
declare(strict_types=1);
abstract class Notificacion
{
public function __construct(
protected string $destinatario,
protected string $mensaje,
) {}
// El flujo es igual para todas
public function enviar(): bool
{
if (!$this->validarDestinatario()) {
throw new InvalidArgumentException("Destinatario no válido");
}
$contenido = $this->formatear();
return $this->entregar($contenido);
}
protected function formatear(): string
{
return trim($this->mensaje);
}
abstract protected function validarDestinatario(): bool;
abstract protected function entregar(string $contenido): bool;
}
class NotificacionEmail extends Notificacion
{
protected function validarDestinatario(): bool
{
return filter_var($this->destinatario, FILTER_VALIDATE_EMAIL) !== false;
}
protected function entregar(string $contenido): bool
{
echo "Correo a {$this->destinatario}: $contenido\n";
return true;
}
}
class NotificacionSms extends Notificacion
{
protected function formatear(): string
{
// Los SMS tienen límite de caracteres
return substr(parent::formatear(), 0, 160);
}
protected function validarDestinatario(): bool
{
return preg_match('/^\+?\d{8,15}$/', $this->destinatario) === 1;
}
protected function entregar(string $contenido): bool
{
echo "SMS a {$this->destinatario}: $contenido\n";
return true;
}
}
$avisos = [
new NotificacionEmail("cliente@empresa.com", "Su pedido fue enviado"),
new NotificacionSms("+59176980507", "Su pedido fue enviado"),
];
foreach ($avisos as $aviso) {
$aviso->enviar();
}
Fíjate en NotificacionSms::formatear(): sobrescribe el método del padre pero lo reutiliza con parent::formatear() antes de recortar. Puedes elegir en cada caso si reemplazas o amplías.
Errores comunes
- Intentar instanciar la clase abstracta.
- Olvidar implementar un método abstracto en la hija.
- Cambiar la firma al implementarlo.
- Declarar abstracto un método privado.
- Usar clase abstracta donde tocaba interfaz, y quedarte sin poder extender otra cosa.
Para cerrar
Una clase abstracta es un molde incompleto: define lo común y obliga a cada hija a completar lo específico. Su valor está en poder compartir código, que es justo lo que una interfaz no permite.
La pregunta para decidir: ¿estas clases comparten código, o solo comparten una capacidad?
En la siguiente lección veremos las interfaces en detalle.
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.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp