Blog

Análisis estático de código PHP con PHPStan

/ php, analisis estatico, phpstan

En el competitivo mundo del desarrollo de software, mantener una calidad de código de primer nivel es crucial. Los errores no solo desperdician tiempo valioso, sino que también pueden costar millones a las empresas. Aquí es donde entran en juego herramientas de análisis estático como PHPStan. PHPStan es un cambio radical para los desarrolladores de PHP, permitiéndoles detectar posibles problemas en su código antes de que siquiera se ejecute. Al analizar minuciosamente tu base de código, PHPStan identifica errores, refuerza los estándares de codificación y resalta áreas para mejorar.

¿Qué es el Análisis Estático?

El análisis estático es una técnica que utilizamos para examinar nuestro código sin ejecutarlo realmente. Piénsalo como una forma de mirar dentro de los entresijos de nuestra base de código, detectando posibles problemas e inconsistencias antes de que causen problemas en un entorno en vivo.

Al analizar el código de manera estática, podemos detectar una variedad de problemas, desde errores de sintaxis hasta errores más sutiles que podrían provocar errores en tiempo de ejecución. A diferencia del análisis dinámico, que requiere ejecutar el código y monitorizar su comportamiento, el análisis estático nos ofrece una instantánea de la salud de nuestro código basándose únicamente en su estructura y contenido. Estos son algunos de los beneficios del análisis estático:

  1. Detección Temprana de Errores: Una de las mayores ventajas del análisis estático es su capacidad para detectar errores tempranos en el proceso de desarrollo. Al identificar problemas antes de que el código se ejecute, podemos abordar los problemas antes de que escalen, ahorrándonos deuda técnica.

  2. Mejora de la Calidad del Código: El análisis estático ayuda a reforzar los estándares y las buenas prácticas de codificación. Puede resaltar áreas donde nuestro código no se ajusta a las directrices establecidas, asegurando que nuestro código se mantenga limpio, legible y fácil de mantener. Esta consistencia es clave para el éxito a largo plazo del proyecto.

  3. Reducción de la Deuda Técnica: Con el tiempo, las soluciones rápidas y apresuradas pueden llevar a una deuda técnica, haciendo que nuestro código sea más difícil de trabajar y más propenso a errores. Las herramientas de análisis estático nos ayudan a identificar y abordar estos problemas temprano, evitando que la deuda técnica se acumule y asegurando un código más saludable.

En esencia, el análisis estático es como tener un revisor experto que revisa constantemente nuestro código, ayudándonos a mantener altos estándares y a evitar posibles escollos. Es un aliado poderoso en nuestra búsqueda de un software robusto y fiable. Y ahí es donde entra PHPStan, llevando estos beneficios a nuestros proyectos PHP con facilidad.

¿Qué es PHPStan?

PHPStan es una herramienta sofisticada de análisis estático diseñada específicamente para PHP. Destaca por su profundo entendimiento de las complejidades de PHP y su capacidad para analizar eficazmente bases de código complejas. PHPStan se integra perfectamente con varios frameworks y bibliotecas de PHP, lo que lo convierte en una herramienta versátil adecuada para cualquier proyecto PHP, desde pequeños scripts hasta aplicaciones empresariales de gran escala. Revisemos algunas de sus características clave:

  1. Inferencia de Tipos: PHPStan sobresale en la inferencia de tipos, lo que significa que puede deducir los tipos de variables y valores de retorno de funciones a lo largo de tu código. Esto le permite detectar errores relacionados con tipos, como pasar un entero donde se espera una cadena, incluso si no has especificado explícitamente los tipos.

  2. Detección de Errores: Una de las principales fortalezas de PHPStan es su capacidad para detectar una amplia gama de errores. Puede identificar variables indefinidas, llamadas incorrectas a métodos y otros problemas comunes que de otro modo podrían pasar desapercibidos. Al detectar estos problemas temprano, PHPStan ayuda a prevenir que causen problemas en producción.

  3. Extensibilidad: PHPStan es altamente extensible, lo que te permite personalizar su comportamiento para satisfacer tus necesidades específicas. Puedes escribir reglas personalizadas para reforzar los estándares de codificación de tu equipo, integrarte con extensiones de terceros y adaptar el análisis para que coincida con los requisitos únicos de tu proyecto.

  4. Análisis Incremental: PHPStan utiliza un sistema de niveles (de 0 a 9) que te permite aumentar gradualmente la rigurosidad del análisis. Esto significa que puedes comenzar con verificaciones básicas y adoptar progresivamente estándares más rigurosos a medida que mejora tu base de código.

Aprovechando estas características, PHPStan permite a los desarrolladores escribir código PHP más limpio y fiable, convirtiéndose en una herramienta indispensable en el conjunto de herramientas de desarrollo PHP moderno.

Configurando PHPStan

Vamos a mejorar el código de nuestro ejemplo anterior, Pruebas de Contrato para Servicios de Infraestructura Confiables, añadiendo análisis de código estático. Usaremos Composer para instalar PHPStan. Simplemente necesitamos ejecutar el siguiente comando en nuestra terminal desde el directorio raíz del proyecto:

composer require --dev phpstan/phpstan

Este comando añade PHPStan al archivo composer.json de nuestro proyecto, asegurando que esté disponible para desarrollo sin ser incluido en el entorno de producción.

Una vez que PHPStan esté instalado, el siguiente paso es configurarlo. Necesitamos crear un archivo llamado phpstan.neon en el directorio raíz de nuestro proyecto. Luego, añadiremos el siguiente contenido al archivo phpstan.neon:

parameters:
    level: max
    paths:
        - src

En esta configuración:

  • El parámetro level se establece en max, lo que indica a PHPStan que realice las siguientes comprobaciones:
    1. comprobaciones básicas, clases desconocidas, funciones desconocidas, métodos desconocidos llamados en $this, número incorrecto de argumentos pasados a esos métodos y funciones, variables siempre indefinidas.
    2. variables posiblemente indefinidas, métodos y propiedades mágicas desconocidas en clases con __call y __get.
    3. métodos desconocidos comprobados en todas las expresiones (no solo en $this), validación de PHPDocs.
    4. tipos de retorno, tipos asignados a propiedades.
    5. comprobación básica de código muerto: siempre falso instanceof y otras comprobaciones de tipos, ramas else muertas, código inalcanzable después de un return; etc.
    6. comprobación de tipos de argumentos pasados a métodos y funciones.
    7. informe de tipo hints faltantes.
    8. informe de tipos de unión parcialmente incorrectos: si llamas a un método que solo existe en algunos tipos en un tipo de unión, el nivel 7 comienza a reportarlo; otras situaciones posiblemente incorrectas.
    9. informe de llamadas a métodos y acceso a propiedades en tipos nulos.
    10. ser estricto con el tipo mixed: la única operación permitida que puedes hacer con él es pasarlo a otro mixed.
  • El parámetro paths especifica los directorios que PHPStan debe analizar. En este ejemplo, lo hemos configurado para analizar el directorio src.

Ejecutando PHPStan

Ahora es el momento de analizar nuestro código. Ejecutémoslo con el comando:

vendor/bin/phpstan analyse

Alternativamente, podemos omitir la creación de un archivo de configuración y ejecutar PHPStan directamente utilizando opciones y argumentos:

vendor/bin/phpstan analyse --level=max src

Nuestro código se ve bien ya que los primeros 6 niveles (de 0 a 5) se ejecutan sin errores. Pero hay algunos errores que debemos corregir. Revisemos la lista completa por archivos:

src/Shared/Infrastructure/Persistence/Doctrine/orm.php

Line 11: Function OtherCode\Shared\Infrastructure\Persistence\Doctrine\provideEntityManager() has parameter $parameters with no value type specified in iterable type array.

src/UserManagement/Application/Contract/UserRepository.php

Line 13: Method OtherCode\UserManagement\Application\Contract\UserRepository::all() return type has no value type specified in iterable type array.

src/UserManagement/Application/UserFinder.php

Line 27: Method OtherCode\UserManagement\Application\UserFinder::all() return type has no value type specified in iterable type array.

src/UserManagement/Infrastructure/Persistence/DoctrineUserRepository.php

Line 28: Method OtherCode\UserManagement\Infrastructure\Persistence\DoctrineUserRepository::all() return type has no value type specified in iterable type array.

src/UserManagement/Infrastructure/Persistence/JsonFileUserRepository.php

Line 24: Only iterables can be unpacked, mixed given in argument #1.
Line 24: Parameter #1 $id of class OtherCode\UserManagement\Domain\User constructor expects int|null, mixed given.
Line 24: Parameter #1 $json of function json_decode expects string, string|false given.
Line 28: Method OtherCode\UserManagement\Infrastructure\Persistence\JsonFileUserRepository::all() return type has no value type specified in iterable type array.
Line 31: Only iterables can be unpacked, mixed given in argument #1.
Line 31: Parameter #1 $id of class OtherCode\UserManagement\Domain\User constructor expects int|null, mixed given.
Line 31: Parameter #1 $json of function json_decode expects string, string|false given.
Line 32: Parameter #2 $array of function array_map expects array, array<int, string>|false given.
Line 39: Parameter #1 $value of function count expects array|Countable, array<int, string>|false given.

Hay varios errores relacionados con los tipos de valores en los iterables. Afortunadamente, estos son bastante simples de resolver. Para más información, consulta Solving PHPStan error “No value type specified in iterable type”.

En nuestro caso, solo necesitamos agregar algunos bloques PHPDoc a nuestro código para proporcionar pistas de tipo adecuadas a los valores de los arrays. Por ejemplo, vamos a corregir la función provideEntityManager en el archivo src/Shared/Infrastructure/Persistence/Doctrine/orm.php:

/**  
 * @param  array<string, array<string, mixed>>  $parameters  
 * @return EntityManager  
 */
 function provideEntityManger(array $parameters = []): EntityManager  
 {

 }

Al añadir bloques PHPDoc, podemos proporcionar fácilmente pistas de tipo para los valores del array.

A continuación, centrémonos en el archivo JsonFileUserRepository.php, ya que contiene numerosos errores. Por ejemplo:

// Parameter #1 $json of function json_decode expects string, string|false given.

json_decode(file_get_contents($path)

Este código puede causar problemas porque si file_get_contents() encuentra un error, devuelve false. Esto hace que json_decode() reciba un tipo de variable incorrecto y falle. Algunas razones por las que file_get_contents() podría fallar incluyen que el archivo no esté en la ruta especificada o que el servidor no tenga permiso para acceder al archivo.

Otro error que debemos abordar en este archivo es:

// Parameter #1 $value of function count expects array|Countable, array<int, string>|false given.

count(glob("directory/*.json"))

Este error ocurre porque la función count() espera que su parámetro sea un array o un objeto que implemente la interfaz Countable. Sin embargo, la función glob(), que se usa para encontrar nombres de ruta que coinciden con un patrón, puede devolver un array de archivos coincidentes o false si ocurre un error.

Consulta este commit para saber más sobre los cambios que hemos realizado.

Conclusión

En este post, hemos explorado el poder de PHPStan para el análisis estático en nuestros proyectos PHP. Cubrimos cómo configurar PHPStan, ejecutarlo e interpretar sus resultados. Vimos de primera mano cómo PHPStan nos ayuda a identificar problemas en nuestro código, haciéndolo más robusto y confiable.

Al usar PHPStan, podemos detectar errores rápidamente, mejorar la calidad del código y reducir la deuda técnica. Para más información, consulta la documentación oficial y los tutoriales.

Siguiente Postt Post Anterior