Larastan es una herramienta por línea de comandos de análisis para aplicar a tus proyectos Laravel y que está basada en PHPStan. PHPStan es una herramienta de análisis de código estático que permite analizar el código PHP, está herramienta te permitirá encontrar bugs sin escribir tests.

Desde hace tiempo llevo buscando una herramienta que analice el código desarrollado de forma estática y que te ayude a subsanar bugs no visibles, o alguna mala práctica de código de la que no eres consciente, nadie es perfecto, es por ello que inicialmente hice pruebas con Sonarqube sobre todo porque me ofrecía la posibilidad de usarlo en varios stacks tecnológicos como podrían ser PHP o Java; pero buscaba algo más sencillo y dentro del entorno del propio proyecto Laravel pero finalmente me decidí por una herramienta ya conocida como PHPStan y buscar la manera de aplicarlo fácilmente en Laravel, esto lo podemos hacer con Larastan, como comentaba PHPStan una herramienta de análisis de código por línea de comandos con varios niveles de exigencia.

PHPStan es una herramienta de análisis estático que permite analizar el código PHP

https://phpstan.org/

Índice

  1. Instalación y funcionamiento
  2. Analizamos el código de ADMIN-APP
  3. Corregimos errores y mejoramos el código
    1. Error 1: should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse
    2. Error 2: Property App\Models\CmCustomer::property does not accept mixed
    3. Error 3: 17 Method App\Http\Controllers\CmCustomerController::index() should return Illuminate\Http\Response but return statement is missing
    4. Error 4: Access to an undefined property App\Http\Resources\CmCustomerResource::$property
    5. Error 5: Expression in empty() is not falsy
    6. Error 6: corregimos varios errores en la documentación de métodos
    7. Error 7: Corregimos errores de programación en app/Http/Controllers/API/CmCustomerController.php
    8. Error 8: Corregimos errores de programación en app/Http/Controllers/API/CmEnterpriseController.php
    9. Error 9: Corregimos errores de tipado en app/Jobs/CmCustomerCreated.php y en app/Jobs/CmCustomerUpdated.php
    10. Error 10: Property is never read, only written en app/Jobs/CmCustomerCreated.php y app/Jobs/CmCustomerUpdated.php

1. Instalación y funcionamiento

2. Analizamos el código de ADMIN-APP

Después de lanzar los errores obtenemos la siguiente información por línea de comandos 86 errores en el nivel 9, el nivel más alto y que es el que suelo aplicar en mis proyectos, en este código de ejemplo lo he ido desarrollando sin pasar la herramienta de PHPStan para tener un pull de errores sobre el que ir desgranando y entendiendo el análisis que nos da PHPStan y cómo actualizarlo:

$ ./vendor/bin/phpstan analyse

Note: Using configuration file /home/xules/xulprocx-local/DOCKER-Projects/Docker-Laravel/cx-laravel-microservices-customerdb-publish/cx-lpm-customerdb-admin/cx-lpm-customerdb-admin-app/phpstan.neon.
 28/28 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ -------------------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/API/CmCustomerController.php                                                                                                     
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------- 
  34     Method App\Http\Controllers\API\CmCustomerController::index() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.    
  58     Expression in empty() is not falsy.                                                                                                               
  61     Method App\Http\Controllers\API\CmCustomerController::store() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.    
  69     Method App\Http\Controllers\API\CmCustomerController::store() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.    
  85     Method App\Http\Controllers\API\CmCustomerController::store() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.    
  98     Method App\Http\Controllers\API\CmCustomerController::show() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.     
  111    Method App\Http\Controllers\API\CmCustomerController::show() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.     
  125    Method App\Http\Controllers\API\CmCustomerController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  145    Expression in empty() is not falsy.                                                                                                               
  148    Method App\Http\Controllers\API\CmCustomerController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  150    Property App\Models\CmCustomer::$identerprise (int) does not accept mixed.                                                                        
  151    Property App\Models\CmCustomer::$customer (string) does not accept mixed.                                                                         
  152    Property App\Models\CmCustomer::$contact (string) does not accept mixed.                                                                          
  153    Property App\Models\CmCustomer::$customerstate (string) does not accept mixed.                                                                    
  154    Property App\Models\CmCustomer::$paymentmethod (string) does not accept mixed.                                                                    
  155    Property App\Models\CmCustomer::$elanguage (string) does not accept mixed.                                                                        
  156    Property App\Models\CmCustomer::$country (string) does not accept mixed.                                                                          
  157    Property App\Models\CmCustomer::$currency (string) does not accept mixed.                                                                         
  158    Property App\Models\CmCustomer::$address (string) does not accept mixed.                                                                          
  171    Method App\Http\Controllers\API\CmCustomerController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  180    Method App\Http\Controllers\API\CmCustomerController::destroy() has parameter $idcustomer with no type specified.                                 
  180    PHPDoc tag @param references unknown parameter: $id                                                                                               
  183    Cannot access property $customer on App\Models\CmCustomer|Illuminate\Database\Eloquent\Collection<App\Models\CmCustomer>|null.                    
  185    Cannot call method delete() on App\Models\CmCustomer|Illuminate\Database\Eloquent\Collection<App\Models\CmCustomer>|null.                         
  191    Method App\Http\Controllers\API\CmCustomerController::destroy() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.  
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/API/CmEnterpriseController.php                                                                                                     
 ------ ---------------------------------------------------------------------------------------------------------------------------------------------------- 
  63     Method App\Http\Controllers\API\CmEnterpriseController::index() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.    
  86     Method App\Http\Controllers\API\CmEnterpriseController::store() has no return type specified.                                                       
  99     Expression in empty() is not falsy.                                                                                                                 
  149    Method App\Http\Controllers\API\CmEnterpriseController::show() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.     
  162    Method App\Http\Controllers\API\CmEnterpriseController::show() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.     
  176    Method App\Http\Controllers\API\CmEnterpriseController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  196    Expression in empty() is not falsy.                                                                                                                 
  199    Method App\Http\Controllers\API\CmEnterpriseController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  201    Property App\Models\CmEnterprise::$enterprise (string|null) does not accept mixed.                                                                  
  202    Property App\Models\CmEnterprise::$description (string) does not accept mixed.                                                                      
  203    Property App\Models\CmEnterprise::$contact (string) does not accept mixed.                                                                          
  204    Property App\Models\CmEnterprise::$estate (string) does not accept mixed.                                                                           
  205    Property App\Models\CmEnterprise::$elanguage (string) does not accept mixed.                                                                        
  206    Property App\Models\CmEnterprise::$country (string) does not accept mixed.                                                                          
  207    Property App\Models\CmEnterprise::$currency (string) does not accept mixed.                                                                         
  215    Method App\Http\Controllers\API\CmEnterpriseController::update() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.   
  229    Method App\Http\Controllers\API\CmEnterpriseController::destroy() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.  
  240    Comparison operation ">" between 0 and 0 is always false.                                                                                           
  241    Method App\Http\Controllers\API\CmEnterpriseController::destroy() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.  
  252    Method App\Http\Controllers\API\CmEnterpriseController::destroy() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.  
 ------ ---------------------------------------------------------------------------------------------------------------------------------------------------- 

 ------ ------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/CmCustomerController.php                                                                                            
 ------ ------------------------------------------------------------------------------------------------------------------------------------- 
  17     Method App\Http\Controllers\CmCustomerController::index() should return Illuminate\Http\Response but return statement is missing.    
  27     Method App\Http\Controllers\CmCustomerController::create() should return Illuminate\Http\Response but return statement is missing.   
  38     Method App\Http\Controllers\CmCustomerController::store() should return Illuminate\Http\Response but return statement is missing.    
  49     Method App\Http\Controllers\CmCustomerController::show() should return Illuminate\Http\Response but return statement is missing.     
  60     Method App\Http\Controllers\CmCustomerController::edit() should return Illuminate\Http\Response but return statement is missing.     
  72     Method App\Http\Controllers\CmCustomerController::update() should return Illuminate\Http\Response but return statement is missing.   
  83     Method App\Http\Controllers\CmCustomerController::destroy() should return Illuminate\Http\Response but return statement is missing.  
 ------ ------------------------------------------------------------------------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/CmEnterpriseController.php                                                                                            
 ------ --------------------------------------------------------------------------------------------------------------------------------------- 
  17     Method App\Http\Controllers\CmEnterpriseController::index() should return Illuminate\Http\Response but return statement is missing.    
  27     Method App\Http\Controllers\CmEnterpriseController::create() should return Illuminate\Http\Response but return statement is missing.   
  38     Method App\Http\Controllers\CmEnterpriseController::store() should return Illuminate\Http\Response but return statement is missing.    
  49     Method App\Http\Controllers\CmEnterpriseController::show() should return Illuminate\Http\Response but return statement is missing.     
  60     Method App\Http\Controllers\CmEnterpriseController::edit() should return Illuminate\Http\Response but return statement is missing.     
  72     Method App\Http\Controllers\CmEnterpriseController::update() should return Illuminate\Http\Response but return statement is missing.   
  83     Method App\Http\Controllers\CmEnterpriseController::destroy() should return Illuminate\Http\Response but return statement is missing.  
 ------ --------------------------------------------------------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmCustomerResource.php                                                   
 ------ ---------------------------------------------------------------------------------------- 
  21     Access to an undefined property App\Http\Resources\CmCustomerResource::$customer.       
  22     Access to an undefined property App\Http\Resources\CmCustomerResource::$customerstate.  
  23     Access to an undefined property App\Http\Resources\CmCustomerResource::$contact.        
  24     Access to an undefined property App\Http\Resources\CmCustomerResource::$sale.           
  25     Access to an undefined property App\Http\Resources\CmCustomerResource::$identerprise.   
  26     Access to an undefined property App\Http\Resources\CmCustomerResource::$paymentmethod.  
  27     Access to an undefined property App\Http\Resources\CmCustomerResource::$elanguage.      
  28     Access to an undefined property App\Http\Resources\CmCustomerResource::$currency.       
  29     Access to an undefined property App\Http\Resources\CmCustomerResource::$country.        
  30     Access to an undefined property App\Http\Resources\CmCustomerResource::$address.        
 ------ ---------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmEnterpriseResource.php                                                 
 ------ ---------------------------------------------------------------------------------------- 
  18     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$enterprise.   
  19     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$description.  
  20     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$contact.      
  21     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$estate.       
  22     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$elanguage.    
  23     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$country.      
  24     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$currency.     
 ------ ---------------------------------------------------------------------------------------- 

 ------ -------------------------------------------------------------------------------------------------- 
  Line   Jobs/CmCustomerCreated.php                                                                        
 ------ -------------------------------------------------------------------------------------------------- 
  16     Property App\Jobs\CmCustomerCreated::$customer has no type specified.                             
  16     Property App\Jobs\CmCustomerCreated::$customer is never read, only written.                       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties                   
  22     Method App\Jobs\CmCustomerCreated::__construct() has parameter $customer with no type specified.  
 ------ -------------------------------------------------------------------------------------------------- 

 ------ -------------------------------------------------------------------------------------------------- 
  Line   Jobs/CmCustomerUpdated.php                                                                        
 ------ -------------------------------------------------------------------------------------------------- 
  17     Property App\Jobs\CmCustomerUpdated::$customer has no type specified.                             
  17     Property App\Jobs\CmCustomerUpdated::$customer is never read, only written.                       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties                   
  23     Method App\Jobs\CmCustomerUpdated::__construct() has parameter $customer with no type specified.  
 ------ -------------------------------------------------------------------------------------------------- 

 ------ ------------------------------------------------- 
  Line   Providers/RouteServiceProvider.php               
 ------ ------------------------------------------------- 
  36     PHPDoc tag @var above a method has no effect.    
  36     PHPDoc tag @var does not specify variable name.  
  60     Cannot access property $id on mixed.             
 ------ ------------------------------------------------- 

 -- ---------------------------------------------------------------------------------------- 
     Error                                                                                   
 -- ---------------------------------------------------------------------------------------- 
     Ignored error pattern #Unsafe usage of new static# was not matched in reported errors.  
 -- ---------------------------------------------------------------------------------------- 

                                                                                                                        
 [ERROR] Found 86 errors    

Veamos el resultado aplicando el nivel 1:

$ ./vendor/bin/phpstan analyse
Note: Using configuration file /home/xules/xulprocx-local/DOCKER-Projects/Docker-Laravel/cx-laravel-microservices-customerdb-publish/cx-lpm-customerdb-admin/cx-lpm-customerdb-admin-app/phpstan.neon.
 28/28 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/CmCustomerController.php                                                                                            
 ------ ------------------------------------------------------------------------------------------------------------------------------------- 
  17     Method App\Http\Controllers\CmCustomerController::index() should return Illuminate\Http\Response but return statement is missing.    
  27     Method App\Http\Controllers\CmCustomerController::create() should return Illuminate\Http\Response but return statement is missing.   
  38     Method App\Http\Controllers\CmCustomerController::store() should return Illuminate\Http\Response but return statement is missing.    
  49     Method App\Http\Controllers\CmCustomerController::show() should return Illuminate\Http\Response but return statement is missing.     
  60     Method App\Http\Controllers\CmCustomerController::edit() should return Illuminate\Http\Response but return statement is missing.     
  72     Method App\Http\Controllers\CmCustomerController::update() should return Illuminate\Http\Response but return statement is missing.   
  83     Method App\Http\Controllers\CmCustomerController::destroy() should return Illuminate\Http\Response but return statement is missing.  
 ------ ------------------------------------------------------------------------------------------------------------------------------------- 

 ------ --------------------------------------------------------------------------------------------------------------------------------------- 
  Line   Http/Controllers/CmEnterpriseController.php                                                                                            
 ------ --------------------------------------------------------------------------------------------------------------------------------------- 
  17     Method App\Http\Controllers\CmEnterpriseController::index() should return Illuminate\Http\Response but return statement is missing.    
  27     Method App\Http\Controllers\CmEnterpriseController::create() should return Illuminate\Http\Response but return statement is missing.   
  38     Method App\Http\Controllers\CmEnterpriseController::store() should return Illuminate\Http\Response but return statement is missing.    
  49     Method App\Http\Controllers\CmEnterpriseController::show() should return Illuminate\Http\Response but return statement is missing.     
  60     Method App\Http\Controllers\CmEnterpriseController::edit() should return Illuminate\Http\Response but return statement is missing.     
  72     Method App\Http\Controllers\CmEnterpriseController::update() should return Illuminate\Http\Response but return statement is missing.   
  83     Method App\Http\Controllers\CmEnterpriseController::destroy() should return Illuminate\Http\Response but return statement is missing.  
 ------ --------------------------------------------------------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmCustomerResource.php                                                   
 ------ ---------------------------------------------------------------------------------------- 
  21     Access to an undefined property App\Http\Resources\CmCustomerResource::$customer.       
  22     Access to an undefined property App\Http\Resources\CmCustomerResource::$customerstate.  
  23     Access to an undefined property App\Http\Resources\CmCustomerResource::$contact.        
  24     Access to an undefined property App\Http\Resources\CmCustomerResource::$sale.           
  25     Access to an undefined property App\Http\Resources\CmCustomerResource::$identerprise.   
  26     Access to an undefined property App\Http\Resources\CmCustomerResource::$paymentmethod.  
  27     Access to an undefined property App\Http\Resources\CmCustomerResource::$elanguage.      
  28     Access to an undefined property App\Http\Resources\CmCustomerResource::$currency.       
  29     Access to an undefined property App\Http\Resources\CmCustomerResource::$country.        
  30     Access to an undefined property App\Http\Resources\CmCustomerResource::$address.        
 ------ ---------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmEnterpriseResource.php                                                 
 ------ ---------------------------------------------------------------------------------------- 
  18     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$enterprise.   
  19     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$description.  
  20     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$contact.      
  21     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$estate.       
  22     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$elanguage.    
  23     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$country.      
  24     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$currency.     
 ------ ---------------------------------------------------------------------------------------- 

 -- ---------------------------------------------------------------------------------------- 
     Error                                                                                   
 -- ---------------------------------------------------------------------------------------- 
     Ignored error pattern #Unsafe usage of new static# was not matched in reported errors.  
 -- ---------------------------------------------------------------------------------------- 

                                                                                                                        
 [ERROR] Found 32 errors          

3. Corregimos errores y mejoramos el código

Partimos inicialmente en el análisis más exigente de 86 errores. No lo repetiré a partir de ahora, pero cada vez que hacemos una corrección, ejecutamos el comando analyse de PHPStan de la siguiente manera:

xules@XXXXXXX:~/....../cx-lpm-customerdb-admin-app$./vendor/bin/phpstan analyse

PHPStan finds bugs in your code without writing tests.

https://phpstan.org/

Vamos al lío:

Error 1: should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse

Este error tiene que ver con la documentación que añadimos a nuestros métodos, en este caso en la generación de código por defecto de Laravel se asigna que la respuesta en el controlador es de tipo Illuminate\Http\Response , pero nosotos en este controlador hemos desarrollado una API con lo que la respuesta real será de tipo Illuminate\Http\JsonResponse.

Cómo veis en este caso no es un error de programación pero si de documentación, y que es tan importante, ya que si alguién utilizada nuestro código y le damos información errónea tenemos un problema.

34 Method App\Http\Controllers\API\CmCustomerController::index() should return Illuminate\Http\Response but returns Illuminate\Http\JsonResponse.

Solución

Sustituir todos los métodos de la API la respuesta incorrecta con la correcta, por ejemplo en index() de CmCustomerController:

Pasamos de:

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()

A:

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\JsonResponse devuelve un Json con los datos de clientes.
     */
    public function index()

Si lanzamos de nuevo el análisis del código comprobamos que ahora el número de errores se ha reducido a 68 errores.

Error 2: Property App\Models\CmCustomer::property does not accept mixed

En este caso se está detectando un error sobre las propiedas definidas en CmCustomer que están predefinidas con la estructura de stdClass, en la documentación de PHPStan en el apartado Universal Object Creates donde hacen referencia directamente a stdClass y a otras estructuras generadas por los framework PHP para que indiquemos en el fichero phpstan.neon para que se tenga en cuenta el uso de eso tipo de clases.

Estos errores se indican tanto en

  Line   Http/Controllers/API/CmCustomerController.php                                                                                   
 ------ -----------------------------------------------------------------------------------------                                                                                            
  150    Property App\Models\CmCustomer::$identerprise (int) does not accept mixed.                                                      
  151    Property App\Models\CmCustomer::$customer (string) does not accept mixed.                                                       
  152    Property App\Models\CmCustomer::$contact (string) does not accept mixed.                                                        
  153    Property App\Models\CmCustomer::$customerstate (string) does not accept mixed.                                                  
  154    Property App\Models\CmCustomer::$paymentmethod (string) does not accept mixed.                                                  
  155    Property App\Models\CmCustomer::$elanguage (string) does not accept mixed.                                                      
  156    Property App\Models\CmCustomer::$country (string) does not accept mixed.                                                        
  157    Property App\Models\CmCustomer::$currency (string) does not accept mixed.                                                       
  158    Property App\Models\CmCustomer::$address (string) does not accept mixed. 

Solución

Añadimos el parámetro de configuración universalObjectCratesClasses tal y cómo se indica en la configuración de la siguiente forma:


parameters:
    ..............

    universalObjectCratesClasses:
        - App\Models\CmCustomer
        - App\Models\CmEnterprise

Si lanzamos de nuevo el análisis del código comprobamos que ahora el número de errores se ha reducido a 52 errores.

Error 3: 17 Method App\Http\Controllers\CmCustomerController::index() should return Illuminate\Http\Response but return statement is missing

Este error afecta a todos los métodos de las clases App\Http\Controllers\CmCustomerController y App\Http\Controllers\CmEnterpriseController que hemos creado y que no se van a desarrollar en este proyecto, entonces o los eliminamos o le indicamos a Larastan que no tenga en cuenta en el análisis estás clases.

Como estamos aprendiendo a utilizar Larastan vamos a ver como indicarle que no analice estas clases:

17 Method App\Http\Controllers\CmCustomerController::index() should return Illuminate\Http\Response but return statement is missing

En la configuración de phpstan.neon podemos excluir este fichero a analizar porque aún no está desarrollado, de la siguiente forma:

    excludePaths:
    excludePaths:
        - ./*/*/FileToBeExcluded.php
        - app/Http/Controllers/CmEnterpriseController.php
        - app/Http/Controllers/CmCustomerController.php

Con está actualización dejamos ya los errores en el código en 38 errors

Error 4: Access to an undefined property App\Http\Resources\CmCustomerResource::$property

En este caso el error no es tal, ya que en la definición de CmCustomerResource que extiende de JSonResource estamos utilizando la estructura base que nos proporciona el método, si tuviesemos que definir todas las propiedades nos veriamos obligados a redifinir la clase al completo, y ese no es el objetivo, veamos como nos indica PHPStan que definamos estas propiedades.

Primero vemos los errores que nos saltan en dos clases App\Http\Resources\CmCustomerResource y App\Http\Resources\CmEnterpriseResource:

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmCustomerResource.php                                                   
 ------ ---------------------------------------------------------------------------------------- 
  21     Access to an undefined property App\Http\Resources\CmCustomerResource::$customer.       
  22     Access to an undefined property App\Http\Resources\CmCustomerResource::$customerstate.  
  23     Access to an undefined property App\Http\Resources\CmCustomerResource::$contact.        
  24     Access to an undefined property App\Http\Resources\CmCustomerResource::$sale.           
  25     Access to an undefined property App\Http\Resources\CmCustomerResource::$identerprise.   
  26     Access to an undefined property App\Http\Resources\CmCustomerResource::$paymentmethod.  
  27     Access to an undefined property App\Http\Resources\CmCustomerResource::$elanguage.      
  28     Access to an undefined property App\Http\Resources\CmCustomerResource::$currency.       
  29     Access to an undefined property App\Http\Resources\CmCustomerResource::$country.        
  30     Access to an undefined property App\Http\Resources\CmCustomerResource::$address.        
 ------ ---------------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------------- 
  Line   Http/Resources/CmEnterpriseResource.php                                                 
 ------ ---------------------------------------------------------------------------------------- 
  18     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$enterprise.   
  19     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$description.  
  20     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$contact.      
  21     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$estate.       
  22     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$elanguage.    
  23     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$country.      
  24     Access to an undefined property App\Http\Resources\CmEnterpriseResource::$currency.     
 ------ ---------------------------------------------------------------------------------------- 

Lo que hacemos en este caso es definir las clases en la documentación de cabecera de la siguiente forma:

  • App\Http\Resources\CmCustomerResource:
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

use App\Models\CmEnterprise;
use App\Http\Resources\CmEnterpriseResource;
/**
 *
 * @property string $customer.
 * @property string $customerstate.
 * @property string $contact.
 * @property string $sale.
 * @property int $identerprise.
 * @property string $paymentmethod.
 * @property string $elanguage.
 * @property string $currency.
 * @property string $country.
 * @property string $address
 */
class CmCustomerResource extends JsonResource
{
  • App\Http\Resources\CmEnterpriseResource:
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

/**
 * @property string $enterprise.
 * @property string $description.
 * @property string $contact.
 * @property string $estate.
 * @property string $elanguage.
 * @property string $country.
 * @property string $currency.
 */
class CmEnterpriseResource extends JsonResource
{

Con está actualización dejamos ya los errores en el código en 21 errors.

Error 5: Expression in empty() is not falsy

El uso del método empty() tiene cierta ambigüedad ya que vale para cualquier tipo de objeto como se puede ver en la documentación de PHP empty() y además no genera una advertencia si la variable no ha sido declarada.

Entonces si analizamos el error en el uso en el código, podemos comprobar que el uso de este método pude dar lugar a generar algún fallo o no ser claro aunque el código funcione, error:

        if($validator->fails()){
            $response = [
                'success' => false,
                'message' => 'Validation Error.'
            ];
            if(!empty($validator->errors())){
                $response['data'] = $validator->errors();
            }
            return response()->json($response, 404);
        }

Una forma más correcta de controlar si tenemos información sobre los errores para devolver sería utilizar el método count(array) que nos devolverá 0 si no hay valores en el array, otra solución sería eliminar este if y devolver el array de errores siempre aunque este vacío.

Solución: sustituimos el uso del método empty() en los arrays por count():

Los errores los teníamos aquí:

 ------ -----------------------------------------------------------------------------------------
  Line   Http/Controllers/API/CmCustomerController.php                                                                                   
 ------ -----------------------------------------------------------------------------------------
  58    Expression in empty() is not falsy.   
  145    Expression in empty() is not falsy. 

 ------ -----------------------------------------------------------------------------------------
  Line   Http/Controllers/API/CmEnterpriseController.php                                                
 ------ -----------------------------------------------------------------------------------------

  99     Expression in empty() is not falsy.                                                            
  196    Expression in empty() is not falsy.  

Vemos la utilización del método count en el caso anterior de Http/Controllers/API/CmCustomerController.php:

        if($validator->fails()){
            $response = [
                'success' => false,
                'message' => 'Validation Error.'
            ];
            if(count($validator->errors()) !== 0){
                $response['data'] = $validator->errors();
            }
            return response()->json($response, 404);
        }

Con esta correción estaríamos ya con solo 17 errores

Error 6: corregimos varios errores en la documentación de métodos

Error 6.1 Error en la documentación del método destroy de CmCustomerController.php:

 ------ -----------------------------------------------------------------------------------------
  Line   Http/Controllers/API/CmCustomerController.php                                                                                   
 ------ -----------------------------------------------------------------------------------------
  180    Method App\Http\Controllers\API\CmCustomerController::destroy() has parameter $idcustomer with no type specified.               
  180    PHPDoc tag @param references unknown parameter: $id   

La solución es fácil nuestro parámetro se llama $idcustomer y lo hemos documentado como $id, esto genera un error en la documentación.

Cambiamos la documentación de :

   /**
    * Remove the specified resource from storage.
    * Eliminamos el recurso específicado del almacenamiento.
    * @param  int  $id
    * @return \Illuminate\Http\JsonResponse
    */
    public function destroy($idcustomer)
    {

A:

   /**
    * Remove the specified resource from storage.
    * Eliminamos el recurso específicado del almacenamiento.
    * @param  int  $idcustomer
    * @return \Illuminate\Http\JsonResponse
    */
    public function destroy($idcustomer)
    {

Ahora nos quedamos con 15 errores.

Error 6.2: vemos que tenemos errores en la clase Providers/RouteServiceProvider.php que es generada automáticamente por Laravel

Como no hemos desarrollado código de momento la vamos a excluir de nuestro análisis, si bien los errores no son significativos ya que PHPDoc hace referencia a una variable que está comentada y entonces no tendría sentido la documentación, y en el caso de $id tendríamos que añadir la definición de la variable.

Estos son los mensajes de error:

 ------ ------------------------------------------------- 
  Line   Providers/RouteServiceProvider.php               
 ------ ------------------------------------------------- 
  38     PHPDoc tag @var above a method has no effect.    
  38     PHPDoc tag @var does not specify variable name.  
  62     Cannot access property $id on mixed.             
 ------ ------------------------------------------------- 

Los añadimos entre los ficheros que no queremos analizar en el fichero phpstan.neon:


    excludePaths:
        - ./*/*/FileToBeExcluded.php
        - app/Http/Controllers/CmEnterpriseController.php
        - app/Http/Controllers/CmCustomerController.php
        - app/Providers/RouteServiceProvider.php

Eliminando este clase del análisis nos quedamos con 12 errores.

Error 7: Corregimos errores de programación en app/Http/Controllers/API/CmCustomerController.php

Vamos a corregir los últimos errores notificados en la clase app/Http/Controllers/API/CmCustomerController.php donde en el método de código hemos cometido un error de programación al no contemplar que un objeto pueda ser null lo que generaría que ser rompiera nuestro código y nuestra Api no diera una respuesta ante ese error.

Estos errores nos los indica así PHPStan:

 ------ ----------------------------------------------------------------- 
  Line   Http/Controllers/API/CmCustomerController.php                    
 ------ ----------------------------------------------------------------- 
  183    Cannot access property $customer on App\Models\CmCustomer|null.  
  185    Cannot call method delete() on App\Models\CmCustomer|null.       
 ------ ----------------------------------------------------------------- 

Si nos vamos al método desarrollado detectamos rápidamente el control que se nos ha olvidado establecer, y encontramos rápidamente un solución sencilla, esto demuestra la gran utilidad de herramientas como PHPStan para controlar nuestro código:

   /**
    * Remove the specified resource from storage.
    * Eliminamos el recurso específicado del almacenamiento.
    * @param  int  $idcustomer
    * @return \Illuminate\Http\JsonResponse
    */
    public function destroy($idcustomer)
    {
      $customer = CmCustomer::find($idcustomer);
      $lcustomer = $customer->customer;
      // No podremos borrar el cliente si tiene pedidos asignados:
      $customer->delete();

      $response = [
        'success' => true,
        'message' => 'El cliente '.$lcustomer.' se ha borrado correctamente',
      ];
      return response()->json($response, 200);
    }

Como se puede ver donde marcamos en negrita el código anterior, ¿qué pasa si nos solicitan borrar un id que no existe? , pues esto lo detectamos con PHPStan. Tengamos en cuenta que estos son ejemplos para analizar el funcionamiento de PHPStan, ya que un error de este tipo lo deberíamos detectar nosotros mismo si desarrollamos correctamente los test unitarios.

La solución es sencilla controlamos si no existe el CmCustomer asociado al id solicitado y si es así devolvemos un error indicando cómo:

   /**
    * Remove the specified resource from storage.
    * Eliminamos el recurso específicado del almacenamiento.
    * @param  int  $idcustomer
    * @return \Illuminate\Http\JsonResponse
    */
    public function destroy($idcustomer)
    {
      $customer = CmCustomer::find($idcustomer);
      if ($customer !== null) {
        $lcustomer = $customer->customer;
        // No podremos borrar el cliente si tiene pedidos asignados:
        $customer->delete();

        $response = [
            'success' => true,
            'message' => 'El cliente '.$lcustomer.' se ha borrado correctamente',
        ];
        return response()->json($response, 200);
      } else {
        return response()->json(
            $response = [
                'success' => false,
                'message' => 'No se ha encontrado el cliente con  id:'.$idcustomer.' que se quería eliminar'
            ],
            404);
      }
    }

Ya casi estamos acando ahora ya solo nos quedan 10 errores que corregir.

Error 8: Corregimos errores de programación en app/Http/Controllers/API/CmEnterpriseController.php

Los errores que tenemos aquí son fáciles de solucionar, el primero en la línea 86 hacer referencia a un problema con la documentación, y el otro de código es una tarea que dejamos sin hacer a propósito, veamos los errores:

 ------ -----------------------------------------------------------------------------------------
  Line   Http/Controllers/API/CmEnterpriseController.php                                                
 ------ -----------------------------------------------------------------------------------------
  86     Method App\Http\Controllers\API\CmEnterpriseController::store() has no return type specified.  
  240    Comparison operation ">" between 0 and 0 is always false.                                      
 ------ -----------------------------------------------------------------------------------------

El primero es muy sencillo de resolver y consiste en que hemos iniciado mal los comentarios de documentación:

     /*
      * Store a newly created resource in storage.
      * Crea un nuevo registro en la base de datos.
      * @param  \Illuminate\Http\Request  $request
      * @return \Illuminate\Http\JsonResponse devolvemos un objeto JsonResponse
      *
      */
    public function store(Request $request)
    {

Como podemos ver nos falta un * al inicio de la documentación lo añadimos y solucionado el error:

     /**
      * Store a newly created resource in storage.
      * Crea un nuevo registro en la base de datos.
      * @param  \Illuminate\Http\Request  $request
      * @return \Illuminate\Http\JsonResponse devolvemos un objeto JsonResponse
      *
      */
    public function store(Request $request)
    {

El segundo hacer referencia a una parte que dejamos sin impletar, voy a mostrar solo la lógica de ese punto y no todo el método ya que se entiende perfectamente:

            $numCustomers = 0;
            // Necesitamos comprobar la integridad del borrado, si se utiliza en clientes no se puede borrar.
            if ($numCustomers > 0) {
            $numCustomers = 0;
            $numCustomers = CmCustomer::where('identerprise', '=', $enterprise->identerprise)->count();
            if ($numCustomers > 0) {

Cómo se puede ver PHPStan nos está diciendo lo evidente que el resultado del if siempre es false ya que la variable siempre es cero, este ejemplo es ilustrativo de algo que nos puede pasar en el desarrollo real, por ejemplo, está condición no estableció al desarrollar CmEnterpriseController ya que el número de clientes lo obtenemos de la clase CmCustomer, y está no estaba definida en este punto, cómo ya le tenemos podemos completar nuestro código de la siguiente forma:

Corregitos estos errores vamos a por los últimos 8 errores a corregir.

Error 9: Corregimos errores de tipado en app/Jobs/CmCustomerCreated.php y en app/Jobs/CmCustomerUpdated.php

 ------ -----------------------------------------------------------------------------------------
  Line   Jobs/CmCustomerCreated.php                                                                        
 ------ -----------------------------------------------------------------------------------------
  18     Property App\Jobs\CmCustomerCreated::$customer has no type specified.                             
  18     Property App\Jobs\CmCustomerCreated::$customer is never read, only written.                       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties                   
  24     Method App\Jobs\CmCustomerCreated::__construct() has parameter $customer with no type specified.  
 ------ -----------------------------------------------------------------------------------------
class CmCustomerCreated implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $customer;
    /**
     * Create a new job instance.
     * Creamos una nueva instancia.
     * @return void
     */
    public function __construct($customer)
    {
        $this->customer = $customer;
    }

dadfasdf

class CmCustomerCreated implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private Array $customer;
    /**
     * Create a new job instance.
     * Creamos una nueva instancia.
     * @return void
     */
    public function __construct(Array $customer)
    {
        $this->customer = $customer;
    }

Corregimos añadimos la identificación para la variable $customer:

Corregimos errores en app/Jobs/CmCustomerUpdated.php:

 ------ -----------------------------------------------------------------------------------------
  Line   Jobs/CmCustomerUpdated.php                                                                        
 ------ -----------------------------------------------------------------------------------------
  17     Property App\Jobs\CmCustomerUpdated::$customer has no type specified.                             
  17     Property App\Jobs\CmCustomerUpdated::$customer is never read, only written.                       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties                   
  23     Method App\Jobs\CmCustomerUpdated::__construct() has parameter $customer with no type specified.  
 ------ -----------------------------------------------------------------------------------------
class CmCustomerUpdated implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private $customer;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct($customer)
    {
        $this->customer = $customer;
    }

Al finalizar la corrección de los errores de tipado

Error 10: Property is never read, only written en app/Jobs/CmCustomerCreated.php y app/Jobs/CmCustomerUpdated.php

Este error es debido a que hemos definido una propiedad como privada en nuestro caso $customer y nos indica que nunca es leída, sino solo escrita, para más información de este error tienes este enlace de PHPStan https://phpstan.org/developing-extensions/always-read-written-properties. El error que nos indican ahora es:

 ------ ---------------------------------------------------------------------------------- 
  Line   Jobs/CmCustomerCreated.php                                                        
 ------ ---------------------------------------------------------------------------------- 
  18     Property App\Jobs\CmCustomerCreated::$customer is never read, only written.       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties   
 ------ ---------------------------------------------------------------------------------- 

 ------ ---------------------------------------------------------------------------------- 
  Line   Jobs/CmCustomerUpdated.php                                                        
 ------ ---------------------------------------------------------------------------------- 
  18     Property App\Jobs\CmCustomerUpdated::$customer is never read, only written.       
         💡 See: https://phpstan.org/developing-extensions/always-read-written-properties   
 ------ ---------------------------------------------------------------------------------- 

Veamos el código donde se da este error, solo mostraré el de CmCustomerCreated ya que el otro es idéntico:

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

use App\Models\CmCustomer;

class CmCustomerUpdated implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    private Array $customer;
    /**
     * Create a new job instance.
     *
     * @return void
     */
    public function __construct(Array $customer)
    {
        $this->customer = $customer;
    }

    /**
     * Execute the job.
     *
     * @return void
     */
    public function handle()
    {
        //
    }
}

Solución

Nosotros vamos a ver posibles soluciones dentro del código sin recurrir a la aplicación de la extensión que nos indica PHPStan:

  • Una solución sería hacer la variable $customer púbica, lo cual no tiene mucho sentido abrir a que se modifique esta propiedad si solo queremos que se modifique a través del constructor.
  • La otra solución más óptima es añadir un getter para la variable $customer, con lo que la variable privada ya es leída dentro de la clase y ya tenemos nuestro código correcto.
    /**
     * @return Array devolvemos los datos del cliente en un array.
     */
    public function getCustomer() {
        return $this->customer;
    }

Después de añadir este método en las dos clases, el número de errores que tenemos es 2: Found errors 2.

Los últimos errores serían:

$ ./vendor/bin/phpstan analyse
Note: Using configuration file /.........../cx-lpm-customerdb-admin/cx-lpm-customerdb-admin-app/phpstan.neon.
 25/25 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

-- ---------------------------------------------------------------------------------------------
     Error                                                                                                                                                                    
-- -----------------------------------------------------------------------------------------------
     Ignored error pattern #Unsafe usage of new static# was not matched in reported errors.                                                                                   
     Ignored error pattern #should return Illuminate\Http\Response but return statement is missing# in path                                                                   
     /home/xules/xulprocx-local/DOCKER-Projects/Docker-Laravel/cx-laravel-microservices-customerdb-publish/cx-lpm-customerdb-admin/cx-lpm-customerdb-admin-app/App/Http/Cont  
     rollers/CmCustomerController was not matched in reported errors.                                                                                                         
 -- ---------------------------------------------------------------------------------------------
                                                                                                                        
 [ERROR] Found 2 errors