Valoracion guardada

Herencia y polimorfismo en PHP: ejemplos prácticos

206 valoraciones de lectores

PHP sigue siendo uno de los lenguajes más utilizados para el desarrollo web. Es la base de gestores de contenidos y plataformas de comercio electrónico tan importantes como WordPress, WooCommerce y PrestaShop.

Aunque es posible crear aplicaciones PHP utilizando únicamente funciones y código procedural, cuando un proyecto crece resulta necesario organizarlo mejor. Aquí es donde entra en juego la programación orientada a objetos y conceptos como la herencia y el polimorfismo.

Estos dos principios permiten crear código más reutilizable, flexible y fácil de mantener. Son especialmente útiles en desarrollos personalizados, plugins, módulos, integraciones con APIs y sistemas con distintos tipos de usuarios, productos, métodos de pago o servicios.

¿Qué es la programación orientada a objetos en PHP?

La programación orientada a objetos, también conocida como POO u OOP, es una forma de estructurar el código mediante clases y objetos.

Una clase define las propiedades y los comportamientos que tendrán sus objetos.

Por ejemplo, podemos crear una clase para representar un producto:

class Producto
{
    protected string $nombre;
    protected float $precio;

    public function __construct(string $nombre, float $precio)
    {
        $this->nombre = $nombre;
        $this->precio = $precio;
    }

    public function obtenerPrecio(): float
    {
        return $this->precio;
    }
}

A partir de esta clase podemos crear diferentes productos:

$producto = new Producto('Camiseta', 29.95);

echo $producto->obtenerPrecio();

Este sistema permite agrupar datos y comportamientos relacionados dentro de una misma estructura.

Dentro de la programación orientada a objetos aparecen conceptos como encapsulación, abstracción, herencia y polimorfismo.

¿Qué es la herencia en PHP?

La herencia permite crear una clase nueva a partir de otra clase existente.

La clase original se denomina clase padre, clase base o superclase. La nueva clase se conoce como clase hija o subclase.

La clase hija hereda las propiedades y métodos accesibles de la clase padre, pero también puede añadir nuevos comportamientos o modificar los existentes.

En PHP se utiliza la palabra reservada extends.

class ProductoDigital extends Producto
{
    private string $urlDescarga;

    public function __construct(
        string $nombre,
        float $precio,
        string $urlDescarga
    ) {
        parent::__construct($nombre, $precio);

        $this->urlDescarga = $urlDescarga;
    }

    public function obtenerUrlDescarga(): string
    {
        return $this->urlDescarga;
    }
}

En este ejemplo, ProductoDigital hereda de Producto.

Eso significa que dispone de las propiedades y métodos definidos en la clase padre, pero además incorpora una URL de descarga.

$curso = new ProductoDigital(
    'Curso de PHP',
    49.95,
    '/descargas/curso-php.zip'
);

echo $curso->obtenerPrecio();
echo $curso->obtenerUrlDescarga();

La llamada a parent::__construct() ejecuta el constructor de la clase padre y evita duplicar la lógica encargada de asignar el nombre y el precio.

¿Para qué sirve la herencia?

La herencia sirve principalmente para reutilizar comportamiento común entre diferentes clases.

Imaginemos una aplicación con varios tipos de productos:

Todos pueden tener un nombre y un precio, pero cada uno necesita comportamientos específicos.

Podríamos definir una clase base:

abstract class Producto
{
    public function __construct(
        protected string $nombre,
        protected float $precio
    ) {
    }

    public function obtenerNombre(): string
    {
        return $this->nombre;
    }

    public function obtenerPrecio(): float
    {
        return $this->precio;
    }
}

Después, cada tipo de producto puede extenderla:

class ProductoFisico extends Producto
{
    public function calcularEnvio(): float
    {
        return 5.95;
    }
}

class ProductoDigital extends Producto
{
    public function calcularEnvio(): float
    {
        return 0;
    }
}

Gracias a la herencia, ambas clases comparten la lógica común del nombre y el precio.

¿Qué es el polimorfismo en PHP?

El polimorfismo permite trabajar con diferentes objetos mediante una interfaz común, aunque cada objeto ejecute una implementación distinta.

El término significa literalmente “muchas formas”.

En programación, un mismo método puede comportarse de manera diferente dependiendo del objeto que lo implemente.

Por ejemplo, podemos definir una interfaz para distintos métodos de pago:

interface MetodoPago
{
    public function pagar(float $importe): bool;
}

Después podemos crear varias implementaciones:

class PagoTarjeta implements MetodoPago
{
    public function pagar(float $importe): bool
    {
        echo "Procesando {$importe} euros mediante tarjeta.";

        return true;
    }
}

class PagoPayPal implements MetodoPago
{
    public function pagar(float $importe): bool
    {
        echo "Procesando {$importe} euros mediante PayPal.";

        return true;
    }
}

class PagoTransferencia implements MetodoPago
{
    public function pagar(float $importe): bool
    {
        echo "Generando instrucciones para transferencia bancaria.";

        return true;
    }
}

Ahora podemos crear una función que acepte cualquier objeto que implemente la interfaz MetodoPago.

function procesarPedido(
    MetodoPago $metodoPago,
    float $importe
): bool {
    return $metodoPago->pagar($importe);
}

La función no necesita saber si el pago se realiza con tarjeta, PayPal o transferencia.

procesarPedido(new PagoTarjeta(), 99.95);
procesarPedido(new PagoPayPal(), 99.95);
procesarPedido(new PagoTransferencia(), 99.95);

La llamada es la misma, pero el comportamiento cambia dependiendo del objeto recibido. Eso es polimorfismo.

Diferencia entre herencia y polimorfismo

Aunque están relacionados, no son exactamente lo mismo.

La herencia permite que una clase reutilice o amplíe el comportamiento de otra.

El polimorfismo permite utilizar diferentes objetos mediante un mismo contrato, aunque cada objeto responda de forma distinta.

La herencia se basa habitualmente en relaciones del tipo “es un”.

Por ejemplo:

El polimorfismo se centra en la posibilidad de intercambiar implementaciones sin cambiar el código que las utiliza.

Clases abstractas en PHP

Una clase abstracta no puede instanciarse directamente. Se utiliza como base para otras clases.

También puede obligar a las clases hijas a implementar determinados métodos.

abstract class Notificacion
{
    abstract public function enviar(string $mensaje): bool;

    public function registrar(string $mensaje): void
    {
        error_log($mensaje);
    }
}

Ahora podemos crear diferentes sistemas de notificación:

class NotificacionEmail extends Notificacion
{
    public function enviar(string $mensaje): bool
    {
        echo "Enviando email: {$mensaje}";

        return true;
    }
}

class NotificacionWhatsApp extends Notificacion
{
    public function enviar(string $mensaje): bool
    {
        echo "Enviando WhatsApp: {$mensaje}";

        return true;
    }
}

Ambas clases deben implementar el método enviar(), pero pueden hacerlo de forma diferente.

Interfaces frente a clases abstractas

Las interfaces definen un contrato que una clase debe cumplir.

Las clases abstractas pueden definir tanto métodos abstractos como métodos ya implementados.

Una interfaz suele utilizarse cuando varias clases necesitan compartir una capacidad, aunque no pertenezcan a la misma jerarquía.

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

Diferentes elementos pueden ser exportables:

class Pedido implements Exportable
{
    public function exportar(): string
    {
        return 'Datos del pedido';
    }
}

class Cliente implements Exportable
{
    public function exportar(): string
    {
        return 'Datos del cliente';
    }
}

Un pedido y un cliente no son el mismo tipo de objeto, pero ambos pueden exportarse.

Ejemplo práctico: sistema de descuentos

Un uso habitual del polimorfismo es crear distintas estrategias de descuento.

interface Descuento
{
    public function calcular(float $precio): float;
}

Creamos varias implementaciones:

class SinDescuento implements Descuento
{
    public function calcular(float $precio): float
    {
        return $precio;
    }
}

class DescuentoPorcentaje implements Descuento
{
    public function __construct(
        private float $porcentaje
    ) {
    }

    public function calcular(float $precio): float
    {
        return $precio - ($precio * $this->porcentaje / 100);
    }
}

class DescuentoFijo implements Descuento
{
    public function __construct(
        private float $importe
    ) {
    }

    public function calcular(float $precio): float
    {
        return max(0, $precio - $this->importe);
    }
}

Podemos aplicar cualquiera de estos descuentos sin modificar la clase que calcula el precio final.

class CalculadoraPrecio
{
    public function calcular(
        float $precio,
        Descuento $descuento
    ): float {
        return $descuento->calcular($precio);
    }
}

Uso:

$calculadora = new CalculadoraPrecio();

echo $calculadora->calcular(
    100,
    new DescuentoPorcentaje(10)
);

echo $calculadora->calcular(
    100,
    new DescuentoFijo(20)
);

Este enfoque evita llenar el código de condiciones del tipo:

if ($tipoDescuento === 'porcentaje') {
    // ...
} elseif ($tipoDescuento === 'fijo') {
    // ...
}

Además, permite añadir nuevos tipos de descuento sin modificar las clases existentes.

Herencia y polimorfismo en WordPress

WordPress mantiene una parte importante de su arquitectura basada en funciones, hooks y callbacks. Sin embargo, la programación orientada a objetos puede utilizarse perfectamente en plugins, temas y desarrollos personalizados.

Por ejemplo, podemos crear una clase base para registrar tipos de contenido personalizados:

abstract class TipoContenido
{
    public function __construct(
        protected string $postType
    ) {
        add_action('init', [$this, 'registrar']);
    }

    abstract public function registrar(): void;
}

Una implementación concreta podría registrar proyectos:

class TipoContenidoProyecto extends TipoContenido
{
    public function registrar(): void
    {
        register_post_type($this->postType, [
            'label' => 'Proyectos',
            'public' => true,
            'show_in_rest' => true,
            'supports' => [
                'title',
                'editor',
                'thumbnail',
            ],
        ]);
    }
}

Después solo necesitamos instanciar la clase:

new TipoContenidoProyecto('proyecto');

También podríamos crear otra clase para testimonios, clientes o casos de éxito reutilizando la misma estructura.

Polimorfismo aplicado a plugins de WordPress

Un plugin puede necesitar enviar avisos por distintos canales.

interface CanalNotificacion
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): bool;
}

Implementación mediante email:

class CanalEmail implements CanalNotificacion
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): bool {
        return wp_mail(
            $destinatario,
            'Nueva notificación',
            $mensaje
        );
    }
}

Implementación mediante una API externa:

class CanalApi implements CanalNotificacion
{
    public function enviar(
        string $destinatario,
        string $mensaje
    ): bool {
        $respuesta = wp_remote_post(
            'https://api.ejemplo.com/notificaciones',
            [
                'body' => [
                    'destinatario' => $destinatario,
                    'mensaje' => $mensaje,
                ],
            ]
        );

        return !is_wp_error($respuesta);
    }
}

El servicio principal no necesita saber qué sistema se está utilizando:

class ServicioNotificaciones
{
    public function __construct(
        private CanalNotificacion $canal
    ) {
    }

    public function notificar(
        string $destinatario,
        string $mensaje
    ): bool {
        return $this->canal->enviar(
            $destinatario,
            $mensaje
        );
    }
}

Herencia y polimorfismo en WooCommerce

WooCommerce utiliza intensamente la programación orientada a objetos.

Sus productos, pedidos, métodos de pago y métodos de envío están representados mediante clases.

Por ejemplo:

Cuando se crea una pasarela de pago personalizada, normalmente se extiende la clase WC_Payment_Gateway.

class WC_Gateway_Pago_Personalizado extends WC_Payment_Gateway
{
    public function __construct()
    {
        $this->id = 'pago_personalizado';
        $this->method_title = 'Pago personalizado';
        $this->method_description =
            'Método de pago desarrollado a medida.';

        $this->has_fields = false;

        $this->init_form_fields();
        $this->init_settings();

        $this->title = $this->get_option('title');
    }

    public function init_form_fields(): void
    {
        $this->form_fields = [
            'enabled' => [
                'title' => 'Activar',
                'type' => 'checkbox',
                'label' => 'Activar método de pago',
                'default' => 'yes',
            ],
            'title' => [
                'title' => 'Título',
                'type' => 'text',
                'default' => 'Pago personalizado',
            ],
        ];
    }

    public function process_payment($order_id): array
    {
        $order = wc_get_order($order_id);

        $order->payment_complete();

        return [
            'result' => 'success',
            'redirect' => $this->get_return_url($order),
        ];
    }
}

Aquí se utiliza herencia porque nuestra pasarela amplía el comportamiento de WooCommerce.

También existe polimorfismo porque WooCommerce puede trabajar con diferentes pasarelas mediante una estructura común.

Para registrar la pasarela:

add_filter(
    'woocommerce_payment_gateways',
    function (array $gateways): array {
        $gateways[] = WC_Gateway_Pago_Personalizado::class;

        return $gateways;
    }
);

Herencia y polimorfismo en PrestaShop

PrestaShop también está construido mediante clases, controladores, módulos y servicios.

Los módulos personalizados suelen extender la clase Module.

class MiModuloPersonalizado extends Module
{
    public function __construct()
    {
        $this->name = 'mimodulopersonalizado';
        $this->version = '1.0.0';
        $this->author = 'Infinit8';
        $this->bootstrap = true;

        parent::__construct();

        $this->displayName =
            $this->l('Mi módulo personalizado');

        $this->description =
            $this->l('Añade una funcionalidad personalizada.');
    }

    public function install(): bool
    {
        return parent::install()
            && $this->registerHook('displayHeader');
    }

    public function hookDisplayHeader(array $params): void
    {
        $this->context->controller->registerStylesheet(
            'mi-modulo-css',
            'modules/' . $this->name . '/views/css/front.css'
        );
    }
}

La clase hereda de Module, por lo que recibe toda la infraestructura necesaria para instalarse, desinstalarse, traducirse, acceder al contexto y registrar hooks.

También es posible aplicar polimorfismo creando diferentes servicios que implementen una interfaz común.

interface ExportadorPedido
{
    public function exportar(Order $pedido): string;
}

Una implementación puede exportar pedidos en JSON:

class ExportadorJson implements ExportadorPedido
{
    public function exportar(Order $pedido): string
    {
        return json_encode([
            'id' => $pedido->id,
            'total' => $pedido->total_paid,
        ]);
    }
}

Otra puede generar CSV:

class ExportadorCsv implements ExportadorPedido
{
    public function exportar(Order $pedido): string
    {
        return implode(',', [
            $pedido->id,
            $pedido->total_paid,
        ]);
    }
}

El sistema que utiliza estos exportadores puede trabajar con ambos sin conocer su implementación interna.

Ventajas de utilizar herencia y polimorfismo

Estos principios aportan varias ventajas a un proyecto PHP.

Reutilización de código

La lógica compartida puede colocarse en una clase base y reutilizarse en las clases hijas.

Menor duplicación

No es necesario repetir las mismas propiedades y métodos en cada clase.

Mayor flexibilidad

Las implementaciones pueden intercambiarse mediante interfaces o clases abstractas.

Mejor mantenimiento

Cada clase tiene una responsabilidad más clara y los cambios quedan mejor aislados.

Facilidad para añadir funcionalidades

Es posible incorporar nuevos métodos de pago, descuentos, exportadores o canales de notificación sin modificar el código existente.

Mejor capacidad de prueba

Las interfaces permiten sustituir servicios reales por implementaciones simuladas durante las pruebas.

Riesgos de abusar de la herencia

La herencia es útil, pero no debe utilizarse para todo.

Una jerarquía con demasiados niveles puede resultar difícil de entender:

Servicio
└── ServicioExterno
    └── ServicioPago
        └── ServicioPagoTarjeta
            └── ServicioPagoTarjetaEuropea

En muchos casos es preferible utilizar composición.

La composición consiste en crear una clase a partir de otros objetos, en lugar de heredar todo su comportamiento.

class PedidoService
{
    public function __construct(
        private MetodoPago $metodoPago,
        private CanalNotificacion $notificador
    ) {
    }
}

Este enfoque suele ser más flexible porque permite combinar comportamientos distintos sin crear largas cadenas de herencia.

Una recomendación habitual es utilizar herencia cuando existe una relación clara entre las clases y composición cuando solo necesitamos incorporar una capacidad concreta.

Conclusión

La herencia y el polimorfismo son dos herramientas fundamentales de la programación orientada a objetos en PHP.

La herencia permite crear clases nuevas reutilizando el comportamiento de una clase existente. El polimorfismo permite trabajar con distintos objetos mediante un contrato común, aunque cada uno ejecute una lógica diferente.

Estos conceptos tienen aplicaciones reales en proyectos PHP:

Utilizados correctamente, permiten crear desarrollos más organizados, mantenibles y preparados para crecer.

En Infinit8 desarrollo soluciones personalizadas con PHP, WordPress, WooCommerce y PrestaShop, desde pequeñas modificaciones hasta plugins, módulos, integraciones y funcionalidades completamente adaptadas a cada proyecto.

Desarrollos personalizadosFreelanceServiciosWebmaster

Preguntas frecuentes

¿Es obligatorio utilizar herencia para programar con objetos en PHP?

No. Es posible trabajar con clases y objetos sin utilizar herencia. En muchos proyectos se combina la herencia con interfaces, traits, composición e inyección de dependencias. La opción adecuada depende de la relación entre las clases y del comportamiento que se quiera reutilizar.

¿WordPress permite utilizar programación orientada a objetos?

Sí. Aunque WordPress conserva una parte importante de su arquitectura procedural, los plugins, temas y desarrollos personalizados pueden organizarse completamente mediante clases, interfaces y servicios. WooCommerce, por ejemplo, utiliza programación orientada a objetos de forma intensiva.

¿Qué es mejor, herencia o composición?

No existe una única respuesta. La herencia es adecuada cuando una clase es una especialización real de otra. La composición suele ser mejor cuando una clase solo necesita utilizar el comportamiento de otro servicio. En proyectos modernos se suele priorizar la composición para evitar jerarquías rígidas y facilitar las pruebas.

¿Necesitas mejorar tu web?