La abstracción consiste en exponer lo esencial de un componente y ocultar los detalles que no necesita quien lo usa.
Hasta ahora hemos trabajado con clases concretas: un Guerrero, un Dron, un Usuario. Son cosas que puedes instanciar (new Usuario()).
Pero a veces necesitamos definir conceptos, no cosas concretas.
- No puedes comprar “un vehículo” en el concesionario. Compras un Ford (Coche) o una Yamaha (Moto). “Vehículo” es una abstracción.
- No puedes conectar “un USB”. Conectas un teclado o una cámara vía USB. USB es un protocolo (interface).
Para manejar estos conceptos en PHP, tenemos dos herramientas: Clases Abstractas e Interfaces.
Clases abstractas: “es un…”
Una clase abstracta es una clase a medio hacer.
- No se puede instanciar (
new ClaseAbstracta❌ Error). - Puede tener métodos normales (con código) y propiedades.
- Puede tener métodos abstractos: firmas sin cuerpo que obligan a los hijos a implementarlos.
Sirve para crear una base común para una familia de objetos.
abstract class MetodoPago {
// Lógica compartida: Todos los métodos de pago tienen moneda
public function __construct(protected string $moneda = 'EUR') {}
// Lógica concreta: Todos convierten divisa igual
public function getMoneda(): string {
return $this->moneda;
}
// EL CONTRATO: Obligo a los hijos a definir CÓMO procesan
// Yo no sé hacerlo, pero sé que ellos deben hacerlo.
abstract public function procesar(float $cantidad): void;
}Ahora creamos las implementaciones:
class PayPal extends MetodoPago {
public function procesar(float $cantidad): void {
echo "Pagando $cantidad {$this->moneda} con PayPal API...";
}
}
class Stripe extends MetodoPago {
public function procesar(float $cantidad): void {
echo "Pagando $cantidad {$this->moneda} con Tarjeta de Crédito...";
}
}Clave: Usamos abstract cuando queremos compartir código (propiedades y métodos comunes) y forzar una estructura en una familia relacionada estrictamente (Jerarquía vertical).
Interfaces: “puede hacer…”
Una Interface es un contrato puro.
- No implementa el cuerpo de los métodos; define sus firmas públicas.
- Puede declarar constantes y, desde PHP 8.4, requisitos de propiedades públicas mediante property hooks.
- Una clase puede implementar múltiples interfaces.
Sirve para definir capacidades independientemente de qué sea el objeto.
Imagina que queremos guardar cosas en un archivo JSON. Un Usuario se puede guardar. Un Pedido se puede guardar. Pero un Usuario NO ES UN Pedido. No tienen nada en común, salvo esa capacidad.
interface ExportableJson {
public function exportar(): string;
}
class Usuario implements ExportableJson {
public function exportar(): string {
return json_encode(['name' => 'Luis']);
}
}
class Pedido implements ExportableJson {
public function exportar(): string {
return json_encode(['id' => 999, 'total' => 50]);
}
}Ahora podemos tener una función que acepte cualquier cosa que cumpla el contrato, sin importarle qué sea:
function guardarEnDisco(ExportableJson $objeto) {
file_put_contents('data.json', $objeto->exportar());
}Esto es Polimorfismo puro. A la función guardarEnDisco le da igual si le pasas un Usuario o un Pedido. Solo le importa que tenga el método exportar.
¿cuándo uso cuál?
Esta tabla resume las diferencias principales.
| Característica | Clase Abstracta | Interface |
|---|---|---|
| Relación | “Es un…” (Identidad) | “Puede hacer…” (Capacidad) |
| Código | Puede tener lógica y estado (variables). | Contratos de métodos y propiedades públicas, sin implementar su lógica. |
| Herencia | Simple (extends). Solo un padre. | Múltiple (implements). Muchas interfaces. |
| Propósito | No repetir código común (DRY). | Desacoplar. Definir protocolos. |
Interfaces en PHP moderno
En PHP moderno, las interfaces son la base del diseño sólido.
- Declaraciones de tipos: Usamos interfaces en los parámetros de las funciones (
function procesar(LoggerInterface $logger)). - Inyección de Dependencias: Nunca pedimos una clase concreta (ej.
MySQLConnection), pedimos una interface (DatabaseConnection). Así, si mañana cambiamos MySQL por PostgreSQL, el código sigue funcionando.
PHP 8.4 también permite que una interfaz exija una propiedad legible, escribible o ambas. Es una función avanzada; para la mayoría de contratos de servicio seguirás usando métodos.