Si le preguntas a los desarrolladores de PHP qué tipo de oportunidad quieren ver en PHP, la mayoría llamará genéricos.
El soporte genérico a nivel de idioma sería la mejor solución. Pero, realizarlos es difícil . Esperamos que algún día el soporte nativo se convierta en parte del idioma, pero probablemente llevará varios años esperar.
Este artículo mostrará cómo, utilizando herramientas existentes, en algunos casos con modificaciones mínimas, podemos obtener el poder de los genéricos en PHP en este momento.
De un traductor: deliberadamente uso papel de rastreo de los "genéricos" en inglés, porque Nunca escuché en comunicación que alguien lo llamara "programación generalizada".
Contenido:
¿Qué son los genéricos?
Esta sección cubre una breve introducción a los genéricos .
Enlaces de lectura:
Ejemplo más simple
Dado que actualmente no es posible definir genéricos a nivel de idioma, tendremos que usar otra gran oportunidad: definirlos en los bloques del muelle.
Ya estamos usando esta opción en muchos proyectos. Echa un vistazo a este ejemplo:
function createUsers(iterable $names): array { ... }
En el código anterior, hacemos lo que es posible a nivel de idioma. Definimos el parámetro $names
como algo que podría enumerarse. También indicamos que la función devolverá una matriz. PHP arrojará un TypeError
si los tipos de parámetros y el valor de retorno no coinciden.
Docblock mejora la comprensión del código. $names
deben ser cadenas y la función debe devolver una matriz de objetos de User
. PHP en sí mismo no hace tales controles. Pero los IDE como PhpStorm entienden esta notación y advierten al desarrollador que no se ha respetado el contrato adicional. Además de esto, las herramientas de análisis estático como Psalm, PHPStan y Phan pueden validar la exactitud de los datos transferidos hacia y desde la función.
Genéricos para determinar claves y valores de tipos enumerados
Arriba está el ejemplo más simple de un genérico. Los métodos más complejos incluyen la capacidad de especificar el tipo de sus claves, junto con el tipo de valores. A continuación hay una forma de describir esto:
function getUsers(): array { ... }
Aquí dice que la matriz devuelta por getUsers
tiene claves de cadena y valores de tipo User
.
Los analizadores estáticos como Psalm, PHPStan y Phan entienden esta anotación y la tienen en cuenta al verificar.
Considere el siguiente código:
function getUsers(): array { ... } function showAge(int $age): void { ... } foreach(getUsers() as $name => $user) { showAge($name); }
Los analizadores estáticos lanzarán una advertencia en la llamada a showAge
con un error, como este: El Argument 1 of showAge expects int, string provided
.
Desafortunadamente, al momento de escribir, PhpStorm no sabe cómo.
Genéricos más sofisticados
Seguimos profundizando en el tema de los genéricos. Considere un objeto que es una pila :
class Stack { public function push($item): void { ... } public function pop() { ... } }
La pila puede aceptar cualquier tipo de objeto. Pero, ¿qué sucede si queremos restringir la pila a solo objetos de tipo User
?
Salmo y Phan apoyan las siguientes anotaciones:
class Stack { public function push($item): void; public function pop(); }
El docblock se usa para transmitir información de tipo adicional, por ejemplo:
$stack = new Stack(); Means that $userStack must only contain Users.
Salmo, al analizar el siguiente código:
$userStack->push(new User()); $userStack->push("hello");
Se quejará de la línea 2 con el error Argument 1 of Stack::push expects User, string(hello) provided.
PhpStorm no admite actualmente esta anotación.
De hecho, hemos cubierto solo una parte de la información sobre genéricos, pero en este momento es suficiente.
Cómo implementar genéricos sin soporte de idiomas
Debe completar los siguientes pasos:
- A nivel de la comunidad, defina estándares genéricos en bloques de acoplamiento (por ejemplo, un nuevo PSR o vuelva a PSR-5)
- Agregue anotaciones de Dockblock a su código
- Utilice IDE que entiendan estas convenciones para realizar análisis estáticos en tiempo real para encontrar inconsistencias.
- Utilice herramientas de análisis estático (como Psalm) como uno de los pasos de CI para detectar errores.
- Defina un método para pasar información de tipo a bibliotecas de terceros.
Estandarización
Por el momento, la comunidad PHP ha adoptado de forma no oficial este formato genérico (la mayoría de las herramientas las admiten y su significado es claro para la mayoría):
function getUsers(): array { ... }
Sin embargo, tenemos problemas con ejemplos simples como este:
function getUsers(): array { ... }
Psalm lo entiende y sabe qué tipo de clave tiene y los valores de la matriz devuelta.
Al momento de escribir, PhpStorm no entiende esto. Al usar esta entrada, extraño el poder del análisis estático en tiempo real que ofrece PhpStorm.
Considera el siguiente código. PhpStorm no entiende que $user
es de tipo User
y $name
es de tipo string:
foreach(getUsers() as $name => $user) { ... }
Si elijo Salmo como herramienta de análisis estático, podría escribir lo siguiente:
function getUsers(): array { ... }
Salmo entiende todo esto.
PhpStorm sabe que la variable $user
es de tipo User
. Pero aún no comprende que la clave de matriz se refiere a una cadena. Phan y PHPStan no entienden las anotaciones específicas del salmo. El máximo que entienden en este código es el mismo que en PhpStorm: el tipo de $user
Se podría argumentar que PhpStorm debería aceptar la array<keyType, valueType>
acuerdo array<keyType, valueType>
. No estoy de acuerdo contigo, porque Creo que este dictado de estándares es tarea del idioma y la comunidad, y las herramientas solo deberían seguirlos.
Supongo que la mayoría de la comunidad PHP recibirá calurosamente el acuerdo descrito anteriormente. Uno que esté interesado en los genéricos. Sin embargo, las cosas se vuelven mucho más complicadas cuando se trata de patrones. Ni PHPStan ni PhpStorm actualmente admiten plantillas. A diferencia de Salmo y Phan. Su propósito es similar, pero si profundizas más, te darás cuenta de que las implementaciones son ligeramente diferentes.
Cada una de las opciones presentadas es una especie de compromiso.
En pocas palabras, es necesario un acuerdo sobre el formato de registro genérico:
- Mejoran la vida de los desarrolladores. Los desarrolladores pueden agregar genéricos a su código y beneficiarse de él.
- Los desarrolladores pueden usar las herramientas que más les gusten y cambiar entre ellas (herramientas) según sea necesario.
- Los creadores de herramientas pueden crear estas herramientas, entendiendo los beneficios para la comunidad y sin temer que algo cambie, o que sean acusados de un "enfoque equivocado".
Psalm tiene toda la funcionalidad necesaria para verificar genéricos. Phan también es así.
Estoy seguro de que PhpStorm introducirá genéricos tan pronto como la comunidad presente un acuerdo de formato único.
Soporte de código de terceros
La parte final del rompecabezas genérico es agregar soporte para bibliotecas de terceros.
Con suerte, tan pronto como aparezca el estándar de definición genérica, la mayoría de las bibliotecas lo implementarán. Sin embargo, esto no sucederá de inmediato. Se usan algunas bibliotecas, pero no tienen soporte activo. Cuando se utilizan analizadores estáticos para validar tipos en genéricos, es importante que se definan todas las funciones que estos genéricos aceptan o devuelven.
¿Qué sucede si su proyecto se basa en bibliotecas de terceros que no tienen soporte genérico?
Afortunadamente, este problema ya se ha resuelto y las funciones de código auxiliar son la solución. Los talones de soporte de Psalm, Phan y PhpStorm .
Los apéndices son archivos normales que contienen firmas de funciones y métodos, pero no los implementan. Al agregar bloques de acoplamiento a los apéndices, las herramientas de análisis estático obtienen la información adicional que necesitan. Por ejemplo, si tiene una clase de pila sin sugerencias de tipo y genéricos como este.
class Stack { public function push($item) { } public function pop() { } }
Puede crear un archivo apéndice que tenga métodos idénticos, pero con la adición de bloques de acoplamiento y sin la implementación de funciones.
class Stack { public function push($item); public function pop(); }
Cuando el analizador estático ve la clase de pila, toma información de tipo del código auxiliar, no del código real.
La capacidad de compartir simplemente el código de stubs (por ejemplo, a través del compositor) sería extremadamente útil, porque permitiría compartir el trabajo realizado.
Pasos adicionales
La comunidad necesita alejarse de los acuerdos y establecer estándares.
¿Quizás la mejor opción sería un PSR genérico?
O tal vez los creadores de los principales analizadores estáticos, PhpStorm, otros IDEs y cualquiera de las personas involucradas en el desarrollo de PHP (para control) podrían desarrollar un estándar que todos usarían.
Tan pronto como aparezca el estándar, todos podrán ayudar con la adición de genéricos a las bibliotecas y proyectos existentes, creando solicitudes de extracción. Y donde esto no es posible, los desarrolladores pueden escribir y compartir apéndices.
Cuando todo esté listo, podemos usar herramientas como PhpStorm para verificar los genéricos en tiempo real mientras escribimos el código. Podemos utilizar herramientas de análisis estático como parte de nuestro CI como garantía de seguridad.
Los genéricos también se pueden implementar en PHP (bueno, casi).
Limitaciones
Hay una serie de limitaciones. PHP es un lenguaje dinámico que le permite hacer muchas cosas "mágicas", como estas . Si usa demasiada magia PHP, puede suceder que los analizadores estáticos no puedan extraer con precisión todos los tipos en el sistema. Si se desconoce algún tipo, las herramientas no podrán utilizar genéricos correctamente en todos los casos.
Sin embargo, la aplicación principal de este análisis es validar su lógica empresarial. Si escribes código limpio, no debes usar demasiada magia.
¿Por qué no solo agregas genéricos a la lengua?
Esa sería la mejor opción. PHP tiene código fuente abierto, ¡y nadie te molesta para clonar las fuentes e implementar genéricos!
¿Qué pasa si no necesito genéricos?
Solo ignora todo lo anterior. Una de las principales ventajas de PHP es que es flexible para elegir el nivel apropiado de complejidad de implementación dependiendo de lo que cree. Con un código único, no necesita pensar en cosas como escribir sugerencias. Pero en proyectos grandes vale la pena aprovechar esas oportunidades.
Gracias a todos los que leyeron a este lugar. Estaré encantado de sus comentarios en el primer ministro.
UPD : ghost404 en los comentarios señaló que PHPStan de la versión 0.12.x entiende las anotaciones de salmo y admite genéricos