Interfaces y clases abstractas en PHP: cuándo usar cada una

Las interfaces y las clases abstractas son los dos mecanismos que PHP ofrece para definir contratos entre componentes. Elegir el incorrecto complica el diseño: las interfaces definen qué hace algo, las clases abstractas comparten cómo lo hace parcialmente. Este artículo muestra la diferencia con ejemplos reales.

Interfaces: definir un contrato puro

Una interfaz declara métodos sin implementación. Cualquier clase que la implemente con implements está obligada a definir todos esos métodos:

<?php
interface Pagable
{
    public function cobrar(float $cantidad): bool;
    public function reembolsar(float $cantidad): bool;
    public function getEstado(): string;
}

class PagoTarjeta implements Pagable
{
    private string $estado = 'pendiente';

    public function cobrar(float $cantidad): bool
    {
        // lógica real contra pasarela de pago
        $this->estado = 'completado';
        return true;
    }

    public function reembolsar(float $cantidad): bool
    {
        $this->estado = 'reembolsado';
        return true;
    }

    public function getEstado(): string
    {
        return $this->estado;
    }
}

class PagoPayPal implements Pagable
{
    private string $estado = 'pendiente';

    public function cobrar(float $cantidad): bool
    {
        $this->estado = 'completado';
        return true;
    }

    public function reembolsar(float $cantidad): bool
    {
        $this->estado = 'reembolsado';
        return true;
    }

    public function getEstado(): string
    {
        return $this->estado;
    }
}

// El código que usa la interfaz no sabe qué implementación tiene
function procesarPedido(Pagable $pago, float $total): void
{
    if ($pago->cobrar($total)) {
        echo "Pago de {$total} € completado. Estado: " . $pago->getEstado();
    }
}

procesarPedido(new PagoTarjeta(), 99.90);
// Pago de 99.9 € completado. Estado: completado
?>

Clases abstractas: compartir lógica parcial

Una clase abstracta puede tener métodos implementados y también métodos abstractos que las subclases deben implementar. No se puede instanciar directamente:

<?php
abstract class Reporte
{
    // Método con implementación compartida
    public function generar(): string
    {
        $cabecera = $this->getCabecera();
        $cuerpo   = $this->getCuerpo();
        $pie      = $this->getPie();
        return $cabecera . "n" . $cuerpo . "n" . $pie;
    }

    protected function getCabecera(): string
    {
        return '=== ' . $this->getTitulo() . ' ===';
    }

    protected function getPie(): string
    {
        return 'Generado el ' . date('d/m/Y');
    }

    // Métodos que cada subclase debe implementar
    abstract protected function getTitulo(): string;
    abstract protected function getCuerpo(): string;
}

class ReporteVentas extends Reporte
{
    protected function getTitulo(): string { return 'Ventas del mes'; }
    protected function getCuerpo(): string { return 'Total vendido: 15.000 €'; }
}

class ReporteUsuarios extends Reporte
{
    protected function getTitulo(): string { return 'Usuarios activos'; }
    protected function getCuerpo(): string { return 'Usuarios este mes: 342'; }
}

echo (new ReporteVentas())->generar();
// === Ventas del mes ===
// Total vendido: 15.000 €
// Generado el 26/06/2026
?>

¿Cuándo usar cada una?

Criterio

Interfaz

Clase abstracta

¿Comparte implementación?

No

¿Herencia múltiple?

Sí (una clase puede implementar varias)

No (solo una clase padre)

¿Relación "es un"?

Capacidad / contrato

Especialización real

¿Instanciable?

No

No

El error más común: elegir abstract cuando necesitas interface

<?php
// MAL: usas abstract pero no hay lógica compartida
abstract class NotificadorAbstracto
{
    abstract public function enviar(string $mensaje): bool;
}

// BIEN: es un contrato puro, usa interface
interface Notificador
{
    public function enviar(string $mensaje): bool;
}

class NotificadorEmail implements Notificador
{
    public function enviar(string $mensaje): bool
    {
        // mail($destinatario, 'Notificación', $mensaje);
        return true;
    }
}

class NotificadorSMS implements Notificador
{
    public function enviar(string $mensaje): bool
    {
        // API SMS
        return true;
    }
}

// Una clase puede implementar ambas si tiene sentido
interface Registrable
{
    public function registrarLog(string $evento): void;
}

class NotificadorAuditado implements Notificador, Registrable
{
    public function enviar(string $mensaje): bool { return true; }
    public function registrarLog(string $evento): void { /* ... */ }
}
?>

La documentación oficial de interfaces en PHP y la de clases abstractas describen las restricciones de cada mecanismo y cómo se combinan con tipos e interfaces extendidas.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP