Curso de PHP

Interfaces en PHP: contratos entre clases

Por Víctor Peña · Publicado el

Interfaces en PHP

Hola, ¿cómo están? Continuando con el curso de PHP, hoy veremos las interfaces. Si tuviera que señalar el concepto que más separa el código de aficionado del profesional, sería este.

¡Empecemos!

Qué es una interfaz

Una interfaz es un contrato: una lista de métodos que una clase se compromete a tener, sin decir nada sobre cómo los implementa.

<?php
interface Exportable
{
    public function exportar(): string;
}

Eso es todo. Sin cuerpos, sin propiedades, sin constructor. Solo la promesa: «quien implemente esto tendrá un método exportar() que devuelve un texto».

Implementar una interfaz

<?php
class Factura implements Exportable
{
    public function exportar(): string
    {
        return "Factura generada en PDF";
    }
}

class ReporteVentas implements Exportable
{
    public function exportar(): string
    {
        return "Reporte generado en Excel";
    }
}

Si una clase declara implements Exportable y no define exportar(), PHP falla de inmediato. El contrato se cumple o el código no arranca.

Para qué sirve realmente

La utilidad no está en la interfaz misma, sino en poder escribir código que dependa del contrato y no de una clase concreta:

<?php
function descargar(Exportable $documento): void
{
    echo $documento->exportar();
}

descargar(new Factura());
descargar(new ReporteVentas());

descargar() funciona con cualquier cosa que sea exportable. Y aquí está lo importante: funcionará también con las clases que escribas dentro de dos años, sin tocar una línea de esta función.

La gran ventaja: varias interfaces a la vez

Como vimos en herencia, una clase solo puede extender una clase. Pero puede implementar todas las interfaces que quiera:

<?php
interface Exportable
{
    public function exportar(): string;
}

interface Imprimible
{
    public function imprimir(): void;
}

interface Archivable
{
    public function archivar(): bool;
}

class Factura implements Exportable, Imprimible, Archivable
{
    public function exportar(): string { return "PDF"; }
    public function imprimir(): void { echo "Imprimiendo..."; }
    public function archivar(): bool { return true; }
}

Esto resuelve el problema de la herencia múltiple sin sus ambigüedades. Una factura es un documento (herencia) y además puede ser exportada, impresa y archivada (interfaces).

Fíjate en el matiz del lenguaje: la herencia describe qué es algo; la interfaz, qué puede hacer. Por eso muchas interfaces terminan en «-able».

Interfaz o clase abstracta

Ya lo vimos en la lección anterior, pero conviene repetir la regla porque es la duda más frecuente:

  • Clase abstracta si las clases comparten código y están emparentadas.
  • Interfaz si solo comparten una capacidad, sin relación entre ellas.

Y la señal decisiva: si necesitas que una clase cumpla dos contratos distintos, tiene que ser con interfaces.

Constantes en interfaces

Una interfaz puede definir constantes, que quedan disponibles en todas las clases que la implementen:

<?php
interface EstadoPedido
{
    const PENDIENTE = 'pendiente';
    const PAGADO = 'pagado';
    const ENVIADO = 'enviado';
}

class Pedido implements EstadoPedido
{
    private string $estado = self::PENDIENTE;
}

Lo que no puede tener son propiedades. Una interfaz describe comportamiento, no datos.

Herencia entre interfaces

Una interfaz puede extender otra, e incluso varias:

<?php
interface Legible
{
    public function leer(): string;
}

interface Escribible
{
    public function escribir(string $contenido): bool;
}

interface Archivo extends Legible, Escribible
{
    public function existe(): bool;
}

Quien implemente Archivo deberá definir los tres métodos.

Interfaces nativas de PHP

PHP trae varias interfaces que hacen que tus objetos se integren con el lenguaje. Ya vimos algunas:

<?php
class Coleccion implements IteratorAggregate, Countable, JsonSerializable
{
    private array $items = [];

    public function getIterator(): ArrayIterator
    {
        return new ArrayIterator($this->items);
    }

    public function count(): int
    {
        return count($this->items);
    }

    public function jsonSerialize(): array
    {
        return $this->items;
    }
}

$c = new Coleccion();

foreach ($c as $item) { }       // gracias a IteratorAggregate
echo count($c);                  // gracias a Countable
echo json_encode($c);            // gracias a JsonSerializable

Las más útiles en el día a día:

Interfaz Qué permite
Countable Usar count() sobre el objeto
IteratorAggregate Recorrerlo con foreach
JsonSerializable Controlar cómo se convierte a JSON
ArrayAccess Acceder con corchetes: $obj['clave']
Stringable Convertirlo a texto con (string)

El uso profesional: inyección de dependencias

Aquí está la razón por la que las interfaces importan tanto en código real.

Compara estas dos versiones:

<?php
// Acoplada: depende de una clase concreta
class ServicioDePedidos
{
    public function confirmar(int $id): void
    {
        $mailer = new MailerSmtp();      // atado a esta implementación
        $mailer->enviar("Pedido $id confirmado");
    }
}
<?php
// Desacoplada: depende del contrato
interface Mailer
{
    public function enviar(string $mensaje): bool;
}

class ServicioDePedidos
{
    public function __construct(
        private Mailer $mailer,
    ) {}

    public function confirmar(int $id): void
    {
        $this->mailer->enviar("Pedido $id confirmado");
    }
}

En la segunda versión puedes pasarle un MailerSmtp en producción, un MailerDePrueba al hacer pruebas, o cambiar mañana a otro proveedor sin tocar ServicioDePedidos.

Eso es lo que hace que un sistema sea mantenible, y es exactamente el mecanismo sobre el que están construidos Laravel y Symfony.

Aquí encajan también las clases anónimas que vimos: crear un Mailer de prueba al vuelo, sin archivo aparte.

Comprobar el contrato

<?php
$factura = new Factura();

var_dump($factura instanceof Exportable);   // true
var_dump($factura instanceof Imprimible);   // true

instanceof funciona con interfaces igual que con clases, y es lo que permite verificar capacidades antes de usarlas.

Cuándo no hacen falta

Seamos honestos: no toda clase necesita una interfaz. Crear una interfaz que implementa una sola clase, y que nunca va a tener otra, es ceremonia sin beneficio.

La interfaz se justifica cuando:

  • Hay o va a haber más de una implementación.
  • Quieres poder sustituir la implementación en pruebas.
  • Estás definiendo un límite entre partes del sistema.

Si nada de eso aplica, una clase normal está bien.

Errores comunes

  • Declarar métodos con cuerpo dentro de una interfaz. No se puede.
  • Poner propiedades. Solo constantes.
  • Usar modificadores distintos de public. Todos los métodos de una interfaz lo son.
  • Cambiar la firma al implementar.
  • Crear interfaces para todo, incluso donde nunca habrá una segunda implementación.

Para cerrar

Una interfaz es una promesa sobre lo que una clase sabe hacer. Su valor está en que permite escribir código contra el contrato y no contra la implementación, que es lo que te deja cambiar piezas sin romper el resto.

Si te llevas una idea: depende de interfaces, no de clases concretas, siempre que haya más de una forma posible de hacer algo.

En la siguiente lección veremos los namespaces.

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