El polimorfismo en la programación orientada a objetos (POO) es la capacidad de que un mismo método se comporte de forma distinta según el objeto que lo ejecute. La palabra viene del griego y significa literalmente «muchas formas»: llamas siempre igual, y lo que ocurre depende de quién está al otro lado.
En PHP esto se traduce en algo muy concreto: puedes escribir código que trabaja con un tipo genérico —una clase abstracta o una interfaz— sin saber ni preocuparte por la clase concreta que recibirá en tiempo de ejecución. Si mañana añades un tipo nuevo, ese código no cambia.
Es el pilar de la POO que menos se entiende cuando se explica en abstracto y el que resulta más obvio cuando se ve funcionando. Vamos directamente al código.
Ejemplo de polimorfismo en PHP
Definimos una clase abstracta con un método que cada hija implementará a su manera:
<?php
abstract class PaymentMethod
{
abstract public function pay(float $amount): string;
}
class CreditCard extends PaymentMethod
{
public function pay(float $amount): string
{
return "Cobrando {$amount} € con tarjeta de crédito";
}
}
class PayPal extends PaymentMethod
{
public function pay(float $amount): string
{
return "Cobrando {$amount} € vía PayPal";
}
}
class BankTransfer extends PaymentMethod
{
public function pay(float $amount): string
{
return "Generando transferencia de {$amount} €";
}
}Y ahora la parte que importa:
<?php
function checkout(PaymentMethod $method, float $amount): void
{
echo $method->pay($amount);
}
checkout(new CreditCard(), 49.90); // Cobrando 49.9 € con tarjeta de crédito
checkout(new PayPal(), 49.90); // Cobrando 49.9 € vía PayPal
checkout(new BankTransfer(), 49.90); // Generando transferencia de 49.9 €La función checkout() no sabe qué clase concreta está recibiendo. Solo sabe que es un PaymentMethod y que, por tanto, responde a pay(). Eso es el polimorfismo.
El valor práctico se ve cuando llega el requisito nuevo. Si mañana hay que aceptar Bizum, escribes una clase más y no tocas checkout(). Compáralo con la alternativa sin polimorfismo:
// Sin polimorfismo: cada método nuevo obliga a tocar esta función
function checkout(string $type, float $amount): void
{
if ($type === 'card') {
// ...
} elseif ($type === 'paypal') {
// ...
} elseif ($type === 'transfer') {
// ...
}
}Ese if encadenado crece indefinidamente y hay que modificarlo cada vez. La versión polimórfica se queda igual para siempre.
Polimorfismo con interfaces
Las clases abstractas funcionan, pero atan a los tipos a una jerarquía de herencia común. Las interfaces resuelven lo mismo sin esa atadura, y en PHP suelen ser la opción preferible:
<?php
interface Exportable
{
public function export(array $rows): string;
}
class CsvExporter implements Exportable
{
public function export(array $rows): string
{
return implode("\n", array_map(
fn (array $row) => implode(',', $row),
$rows
));
}
}
class JsonExporter implements Exportable
{
public function export(array $rows): string
{
return json_encode($rows, JSON_PRETTY_PRINT);
}
}
function download(Exportable $exporter, array $rows): string
{
return $exporter->export($rows);
}CsvExporter y JsonExporter no comparten ningún padre. No tienen nada en común salvo el compromiso de responder a export(). Y eso basta: download() acepta las dos.
Una clase puede implementar varias interfaces a la vez, mientras que solo puede extender de una clase. Por eso, cuando lo único que necesitas es un contrato compartido, la interfaz es casi siempre la respuesta correcta. Este uso de las interfaces como contrato está desarrollado en el artículo sobre abstracción en POO.
Polimorfismo en Laravel
Si trabajas con Laravel llevas usando polimorfismo desde el primer día, aunque no lo llamaras así.
Cuando configuras el driver de caché a redis, file o database, la llamada Cache::get('clave') es exactamente la misma. Lo que cambia es la implementación de Illuminate\Contracts\Cache\Store que hay detrás. Lo mismo con los drivers de sesión, de cola, de correo o de sistema de ficheros: la fachada expone un contrato, y el contenedor resuelve la clase concreta según tu configuración.
Las relaciones polimórficas de Eloquent (morphTo, morphMany) llevan la misma idea a la base de datos: un modelo Comment puede pertenecer a un Post o a un Video sin necesitar dos tablas ni dos claves foráneas.
PHP no tiene sobrecarga de métodos
Aquí hay una confusión muy frecuente, sobre todo si vienes de Java o C#. En esos lenguajes se distingue entre polimorfismo por sobrescritura (una clase hija redefine el método del padre) y por sobrecarga (varios métodos con el mismo nombre y distinta firma en la misma clase).
PHP no soporta la sobrecarga. Esto es un error fatal:
class Report
{
public function generate(string $format): string { }
public function generate(string $format, int $limit): string { }
// Fatal error: Cannot redeclare Report::generate()
}Lo que PHP ofrece en su lugar son los argumentos opcionales, los tipos union y los argumentos variádicos:
class Report
{
public function generate(string $format, ?int $limit = null): string
{
// ...
}
}Ojo con el término «sobrecarga» en la documentación de PHP: allí se refiere a los métodos mágicos __get(), __set() y __call(), que son otra cosa distinta.
Conclusiones
El polimorfismo permite que un mismo método se comporte de forma distinta según el objeto que lo ejecuta. En PHP se consigue mediante clases abstractas o interfaces, y su valor está en poder escribir código que depende del contrato y no de la implementación concreta.
Con él, añadir un tipo nuevo significa escribir una clase nueva en lugar de modificar el código existente. Es la diferencia entre un sistema que crece y uno que se llena de condicionales cada vez más largos.
Preguntas frecuentes
¿Qué diferencia hay entre polimorfismo y herencia? La herencia es el mecanismo por el que una clase recibe los métodos de otra. El polimorfismo es la consecuencia de que esas clases puedan implementarlos de forma distinta y ser usadas indistintamente. La herencia es una de las vías para conseguir polimorfismo, pero no la única: las interfaces lo permiten sin herencia.
¿Existe la sobrecarga de métodos en PHP? No. PHP no permite declarar dos métodos con el mismo nombre en una clase. Se sustituye con parámetros opcionales, tipos union o argumentos variádicos. Lo que la documentación de PHP llama «sobrecarga» son los métodos mágicos __get(), __set() y __call().
¿Es mejor una clase abstracta o una interfaz para conseguir polimorfismo? La interfaz cuando solo necesitas definir un contrato, porque una clase puede implementar varias y no queda atada a una jerarquía. La clase abstracta cuando además quieres compartir código ya implementado entre los tipos.
¿Qué son las relaciones polimórficas en Laravel? Son relaciones de Eloquent (morphTo, morphMany, morphToMany) en las que un modelo puede asociarse a más de un tipo de modelo distinto usando una única tabla, guardando el tipo y el identificador del modelo relacionado.
¿Cuáles son los cuatro pilares de la POO? Abstracción, encapsulación, herencia y polimorfismo. La abstracción modela lo esencial de una entidad, la encapsulación protege su estado interno, la herencia permite reutilizar y extender clases, y el polimorfismo permite que un mismo método actúe de forma distinta según el objeto.
Si quieres dominar PHP 8 de principio a fin, con la POO aplicada a proyectos reales, tienes la Ruta de aprendizaje de PHP 8.