Generics en PHP: La tipificación que te faltaba

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: Por qué los desarrolladores de PHP sueñan con genéricos

\n\n

Si alguna vez has escrito en Java, C# o TypeScript, y luego has vuelto a PHP, seguramente has sentido una ligera decepción. En esos lenguajes existe un elegante sistema de tipos genéricos (Generics) que permite crear estructuras de datos flexibles pero seguras en cuanto a tipos. En PHP, durante mucho tiempo reinó la "anarquía de tipificación": los arrays podían contener cualquier cosa, y el IDE solo resaltaba los errores más obvios.

\n\n

Pero los tiempos cambian. A partir de PHP 7.0 y especialmente en la era de PHP 8.0/8.1/8.2, la comunidad encontró la manera de usar Generics en PHP. Sí, a nivel de sintaxis del lenguaje todavía no existen (y probablemente no lo harán en los próximos años), pero eso no significa que no puedas disfrutar de todas las ventajas de la programación genérica hoy mismo.

\n\n

En este artículo, analizaremos cómo funciona la tipificación en PHP con Generics, qué herramientas existen para ello (PHPStan, Psalm) y cómo escribir código que sea a la vez flexible y seguro. ¡Abróchate el cinturón, que se pone intenso!

\n\n

¿Qué son los Generics y por qué se necesitan en PHP?

\n\n

Generics (tipos genéricos) es un mecanismo que permite parametrizar clases, interfaces y funciones con tipos. En lugar de escribir una implementación separada para cada tipo de dato, creas una universal y especificas el tipo concreto al usarla.

\n\n

Imagina que estás escribiendo una clase para una cola (Queue). Sin genéricos, solo puedes almacenar un tipo de dato o usar mixed (lo que anula toda la tipificación). Con Generics, puedes decir: "Esta es una cola de números enteros" o "Esta es una cola de objetos User".

\n\n

El problema de la tipificación "cruda"

\n\n

Observa este código típico de PHP:

\n\n
<?php\n\nclass Collection {\n    private array $items = [];\n\n    public function add($item): void {\n        $this->items[] = $item;\n    }\n\n    public function get(int $index) {\n        return $this->items[$index] ?? null;\n    }\n}\n\n$users = new Collection();\n$users->add(new User('Alice'));\n$users->add(new User('Bob'));\n$users->add('esto es un string, no un usuario'); // ¡Error! Pero PHP calla\n\n$user = $users->get(2); // Devuelve un string, el IDE no sospecha nada\n$user->getName(); // Error fatal en tiempo de ejecución\n
\n\n

Este es el problema clásico del desarrollador de PHP: falta de control de tipos en la etapa de escritura del código. Solo te enteras del error en tiempo de ejecución, cuando el usuario ya ha hecho clic en el botón. Los Generics resuelven este problema de raíz.

\n\n

Generics en PHP: Cómo funciona en la práctica

\n\n

Dado que la sintaxis nativa de Generics no existe en PHP, utilizamos anotaciones en PHPDoc que son procesadas por analizadores estáticos — PHPStan y Psalm. Estas herramientas verifican que el código cumpla con los tipos declarados, emulando de hecho el funcionamiento de los genéricos.

\n\n

Sintaxis básica: tipos plantilla

\n\n

Supongamos que queremos hacer que nuestra colección sea segura en cuanto a tipos. Así es como se ve usando Generics a través de PHPDoc:

\n\n
<?php\n\n/**\n * @template T\n */\nclass GenericCollection {\n    /** @var array<int, T> */\n    private array $items = [];\n\n    /**\n     * @param T $item\n     */\n    public function add($item): void {\n        $this->items[] = $item;\n    }\n\n    /**\n     * @param int $index\n     * @return T|null\n     */\n    public function get(int $index) {\n        return $this->items[$index] ?? null;\n    }\n}\n\n// Ahora lo usamos:\n/** @var GenericCollection<User> $users */\n$users = new GenericCollection();\n$users->add(new User('Alice'));\n$users->add('string'); // PHPStan mostrará un error: Expected type User, got string\n\n$user = $users->get(0); // PHPStan sabe que es User\necho $user->getName(); //                

Blogs

Book Recommendations