Blog

Static Analysis of PHP Code with PHPStan

/ php, static code analysis, phpstan

In the competitive landscape of software development, maintaining top-notch code quality is crucial. Bugs not only waste valuable time but can also cost companies millions. This is where static analysis tools like PHPStan come into play. PHPStan is a game-changer for PHP developers, enabling them to detect potential issues in their code before it even runs. By thoroughly analyzing your codebase, PHPStan identifies bugs, enforces coding standards, and highlights areas for improvement.

What is Static Analysis?

Static analysis is a technique we use to examine our code without actually executing it. Think of it as a way to peer into the inner workings of our codebase, spotting potential issues and inconsistencies before they cause problems in a live environment.

By analyzing the code statically, we can catch a range of issues, from syntax errors to more subtle bugs that could lead to runtime errors. Unlike dynamic analysis, which requires running the code and monitoring its behavior, static analysis gives us a snapshot of our code's health based solely on its structure and content. These are some of the benefits of static analysis:

  1. Detecting Bugs Early: One of the biggest advantages of static analysis is its ability to catch bugs early in the development process. By identifying issues before the code is run, we can address problems before they escalate, saving us from costly and time-consuming fixes down the line.

  2. Improving Code Quality: Static analysis helps enforce coding standards and best practices. It can highlight areas where our code doesn't conform to established guidelines, ensuring that our code remains clean, readable, and maintainable. This consistency is key to long-term project success.

  3. Reducing Technical Debt: Over time, quick fixes and rushed solutions can lead to technical debt, making our code harder to work with and more prone to errors. Static analysis tools help us identify and address these issues early, preventing technical debt from accumulating and ensuring a healthier code.

In essence, static analysis is like having an expert reviewer constantly checking our code, helping us maintain high standards and avoid potential pitfalls. It’s a powerful ally in our quest for robust, reliable software. And that's where PHPStan comes in, bringing these benefits to our PHP projects with ease.

What is PHPStan?

PHPStan is a sophisticated static analysis tool specifically designed for PHP. It stands out due to its deep understanding of PHP's intricacies and its ability to effectively analyze complex codebases. PHPStan seamlessly integrates with various PHP frameworks and libraries, making it a versatile tool suitable for any PHP project, from small scripts to large, enterprise-level applications. Let's review some of the key features:

  1. Type Inference: PHPStan excels at type inference, which means it can deduce the types of variables and function return values throughout your code. This allows it to catch type-related errors, such as passing an integer where a string is expected, even if you haven't explicitly specified types.

  2. Error Detection: One of PHPStan's core strengths is its ability to detect a wide range of errors. It can identify undefined variables, incorrect method calls, and other common pitfalls that might otherwise slip through the cracks. By catching these issues early, PHPStan helps prevent them from causing problems in production.

  3. Extensibility: PHPStan is highly extensible, allowing you to customize its behavior to suit your specific needs. You can write custom rules to enforce your team's coding standards, integrate with third-party extensions, and tailor the analysis to match your project's unique requirements.

  4. Incremental Analysis: PHPStan uses a level system (from 0 to 9) that allows you to gradually increase the strictness of the analysis. This means you can start with basic checks and progressively adopt more rigorous standards as your codebase improves.

By leveraging these features, PHPStan empowers developers to write cleaner, more reliable PHP code, making it an indispensable tool in the modern PHP development toolkit.

Setting Up PHPStan

Let's enhance the code of our previous example, Contract Testing for Reliable Infrastructure Services by adding static code analysis. We'll use Composer to install PHPStan. We simply need to execute the following command in our terminal from the project's root directory:

composer require --dev phpstan/phpstan

This command adds PHPStan to our project's composer.json file, ensuring it's available for development without being included in the production environment.

Once PHPStan is installed, the next step is to configure it. We need to create a file named phpstan.neon in our project’s root directory. Then we will add the following content to the phpstan.neon file:

parameters:
    level: max
    paths:
        - src

In this configuration:

  • The level parameter is set to max, which tells PHPStan to perform the following checks:
    1. basic checks, unknown classes, unknown functions, unknown methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables.
    2. possibly undefined variables, unknown magic methods and properties on classes with __call and __get.
    3. unknown methods checked on all expressions (not just $this), validating PHPDocs.
    4. return types, types assigned to properties.
    5. basic dead code checking - always false instanceof and other type checks, dead else branches, unreachable code after return; etc.
    6. checking types of arguments passed to methods and functions.
    7. report missing typehints.
      1. report partially wrong union types - if you call a method that only exists on some types in a union type, level 7 starts to report that; other possibly incorrect situations
    8. report calling methods and accessing properties on nullable types
    9. be strict about the mixed type - the only allowed operation you can do with it is to pass it to another mixed
  • The paths parameter specifies the directories PHPStan should analyze. In this example, we’ve set it to analyze the src directory.

Running PHPStan

Now is time to analyze the our code, let's run it with the command:

vendor/bin/phpstan analyse

Alternatively, we can skip creating a configuration file and run PHPStan directly using options and arguments:

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

Our code looks good as the first 6 levels (from 0 to 5) runs without errors. But there are some errors that we should fix, let's review the complete list by files:

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.

There are several errors related to value types in iterables. Fortunately, these are quite simple to resolve. For more information, check out Solving PHPStan error “No value type specified in iterable type”.

In our case, we just need to add some PHPDoc to our code to properly type hint the array values. For example, let's fix the provideEntityManger function in the src/Shared/Infrastructure/Persistence/Doctrine/orm.php file:

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

 }

By adding PHPDoc blocks, we can easily type hint the array values.

Next, let's focus on the JsonFileUserRepository.php file, as it contains numerous errors. For example:

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

json_decode(file_get_contents($path)

This code can cause problems because if file_get_contents() encounters an error, it returns false. This leads to json_decode() receiving an incorrect variable type and failing. Some reasons file_get_contents() might fail include the file not being at the specified path or the server lacking permission to access the file.

Another error we need to address in this file is:

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

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

This error occurs because the count() function expects its parameter to be either an array or an object that implements the Countable interface. However, the glob() function, which is used to find path names matching a pattern, can return an array of matching files or false if an error occurs.

Check this commit to know more about the changes we have done.

Conclusion

In this post, we've explored the power of PHPStan for static analysis in our PHP projects. We covered how to set up PHPStan, run it, and interpret its results. We saw firsthand how PHPStan helps us identify issues in our code, making it more robust and reliable.

By using PHPStan, we can catch bugs early, improve code quality, and reduce technical debt. For more information, check out the official documentation and tutorials.

Next Post Previous Post