Curso de PHP

Clases abstractas en PHP: qué son y cuándo usarlas

Por Víctor Peña · Publicado el

Clases abstractas en PHP

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. Usa public o protected.
  • 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 No
Propiedades No
Constructor 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.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp