Ejecutando PHPstan en Drupal 10

El logo de phpstan.

PHPStan es una herramienta fantástica que revisa nuestro código como si fuera ejecutado por PHP. Nos permite buscar potenciales bugs que pueden no ser fáciles de localizar y también nos permite aprender sobre buenas prácticas a la hora de desarrollar.

El mayor problema es que no sabe cómo revisar los proyectos de Drupal y no comprende la estructura de controladores, entidades y plugins entre otras clases de Drupal. Por esa razón si intentamos ejecutar PHPStan en Drupal no obtendremos los resultados esperados ya que nos lanzará muchos errores.

Pero como siempre sucede con Drupal, existe una extensión que nos permite ejecutarlo, y hay que darle las gracias a Matt Glaman que ha desarrollado una extensión para su integración.

Instalando PHPStan en Drupal

Para instalarlo necesitamos Composer y podemos añadirlo al proyecto con el siguiente comando: 

$ composer require  --dev phpstan/phpstan phpstan/extension-installer mglaman/phpstan-drupal phpstan/phpstan-deprecation-rules

Los paquetes son los siguientes:

phpstan/phpstan: El paquete base, imprescindible.

phpstan/extension-installer: Provee un plugin para composer el cual permite cargar extensión de PHPStan y ejecutarlas.

mglaman/phpstan-drupal: El parque te que hace magia he integra PHPStan con Drupal, ha sido desarrollado por Matt Glaman.

phpstan/phpstan-deprecation-rules: Un paquete que nos ayuda a localizar código obsoleto.

Configurando PHPStan para Drupal:

Lo siguiente es configurarlo, y se hace con un pequeño archivo el cual se llama “phpstan.neon” y hay que ponerlo en la raíz del proyecto.

La configuración más básica es la siguiente:

parameters:
  level: 0
  paths:
    - web/modules/custom

Esa configuración ejecutará las comprobaciones de nivel 0 de PHPStan y en el path web/modules/custom que es donde deberían ir nuestros módulos personalizados.

Los niveles disponibles:

Pero que son los niveles, veamos los que nos provee rápidamente, aunque para mayor información podemos consultar la documentación oficial:

0: Comprobaciones básicas como clases desconocidas, funciones y métodos no definidos, variables no definidas...

1: Posibles variables no definidas y llamadas a métodos o propiedades maginas en clases.

2: Llamadas a métodos desconocidos (el nivel 0 solo comprueba los métodos de $this), valida además la documentación de PHPDocs.

3: los return types y los tipos asignados a las propiedades.

4: Comprobaciones de código al que no se llega, instanceof que siempre son falsos, else a los que nunca se llega...

5: Comprueba los tipos de los argumentos de funciones y métodos.

6: Comprueba los return type no definidos.

7: Comprueba los union types mal definidos.

8: Comprueba las llamadas incorrectas de métodos en null.

9: Comprueba el tipo “mixed”, solo permite pasar un “mixed” a otro “mixed”.

Hay que tener en cuenta que si usamos el nivel 5, también se ejecutarán los anteriores, es decir, los 0, 1, 2, 3 y 4 además del 5.

Personalmente, ejecutaría el nivel 0 como mínimo en todos los proyectos, si trabajan juniors lo subiría al 2 para evitar ciertos problemas y que aprendan, del 5 no pasaría a no ser que se quiera mejorar mucho la calidad del código; pero puede implicar reescribir algo de código o reestructurar incluso alguna clase si llegamos al nivel 9. Yo llego al nivel 8 en proyectos de Symfony, en Drupal nunca paso del 5 por comodidad. 

Ejecutando PHPStan:

Ejecutarlo es muy sencillo, podemos hacerlo con la siguiente línea:

$ ./vendor/bin/phpstan

Al ejecutarlo leerá el archivo phpstan.neon para obtener la configuración.

Podemos limitar la memoria que utiliza con la opción –memory-limit:

$ ./vendor/bin/phpstan --memory-limit=1G

Esto nos ejecutará PHPStan y podemos ver lo que nos devuelve, por ejemplo: 

./vendor/bin/phpstan

Note: Using configuration file phpstan.neon.

12/12 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

------ ----------------------------------
 Line  mi_modulo.module
------ ----------------------------------
  37  Undefined variable: $currentUser
------ ----------------------------------

------ ------------------------------------------------------------------------------
 Line src/Form/CreateEntityForm.php
------ ------------------------------------------------------------------------------
  28 \Drupal calls should be avoided in classes, use dependency injection instead
------ ------------------------------------------------------------------------------

Por defecto PHPStan nos devuelve los errores como una tabla, pero podemos usar otros formatos como json para su posterior analisis: 

$ ./vendor/bin/phpstan --error-format=json --no-progress --ansi > phpstan_analysis.json

Problema con Drupal y PHPStan 

Aunque la extensión de Matt Glaman nos ayude a ejecutar PHPStan en Drupal no nos libramos de problemas, y es que cuando se realiza la inyección de dependencias en Drupal se llama a un método llamado “create”: 

public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
  return new static(
    $configuration,
    $plugin_id,
    $plugin_definition
  );
}

PHPStan no considera bueno el uso de “new static” de modo que nos devuelve un error, pero es tan común que existe documentación al respecto.

Y aunque como bien reporta PHPStan se debería minimizar el uso de llamadas a “new static” en Drupal son necesarias, de modo que la solución más sencilla es ignorar esos errores por mucho que nos pese, podemos hacerlo añadiendo una línea al archivo phpstan.neon y nos quedaría de la siguiente manera: 

parameters:
  level: 0
  paths:
    - web/modules/custom
  ignoreErrors:
    - '#Unsafe usage of new static\(\)#'

No es lo mejor, pero es una buena solución para Drupal. 

Conclusiones: 

PHPStan es una herramienta fantástica que siempre deberíamos usarla en nuestros proyectos ya que nos permitirá minimizar los errores y mejorar nuestro código a la vez que aprendemos. También sería muy recomendable añadirla a los procesos de despliegue para evitar que código sin comprobar llegue a producción. 

Comparte este artículo:
Publicado por Borja
Image

Me metí en la aventura de Drupal con la versión 6, y aquí estoy, 10 años después, escribiendo articulos y haciendo videos sobre Drupal, quien me lo iba a decir. Aunque he probado otros framworks y cms, me quedo con Drupal de lejos, pero Symfony y Django estan entre mis favoritos. Aficionado a la montaña, la bicicleta, y el comer, de eso que no falte.