Laravel Static Code Analysis with PHPStan
Tags: laravel, codequality, tutorial, php
Published: 2022-02-26 12:51:24

Code analysis is the process of testing and evaluating a program either statically or dynamically.

Dynamic analysis is the process of testing and evaluating a program — while software is running. It addresses the diagnosis and correction of bugs, memory issues, and crashes of a program during its execution. While static code analysis is a method of evaluating a program by examining the source code before its execution. It is done by analyzing a set of code against a set of coding rules.

Static analysis takes place before software testing begins. It guarantees that the code you pass on to testing is the highest quality possible. It also provide an automated feedback loop so the developers will know early on which in turn make it easier, and cheaper, to fix those problems.

Code analysis is the process of testing and evaluating a program either statically or dynamically.

Dynamic analysis is the process of testing and evaluating a program — while software is running. It addresses the diagnosis and correction of bugs, memory issues, and crashes of a program during its execution. While static code analysis is a method of evaluating a program by examining the source code before its execution. It is done by analyzing a set of code against a set of coding rules.

Static analysis takes place before software testing begins. It guarantees that the code you pass on to testing is the highest quality possible. It also provide an automated feedback loop so the developers will know early on which in turn make it easier, and cheaper, to fix those problems.

In this tutorial we are going to discuss:

- [Code Analysis Jargons](#code-analysis-jargons)
  - [Measurements](#measurements)
  - [Code Structure](#code-structure)
  - [Complexity](#complexity)
  - [Security Issues](#secuirty-issues)
- [What Does PHPStan Bring?](#what-does-phpstan-bring)
  - [Running PHPStan for the First Time](#running-phpstan-for-the-first-time)
  - [Rule Levels](#rule-levels)
- [How to Use PHPStan?](#how-to-use-phpstan)
  - [Installation](#installation)
  - [Configuration File](#configuration-file)
  - [Ignoring Errors](#ignoring-errors)
  - [The Baseline](#the-baseline)
  - [Adding PHPStan to your CI/CD](#adding-phpstan-to-your-cicd)
- [Limitations of Static Analysis](#limitations-of-static-analysis)
- [Helpful Links](#helpful-links)

---
##Code Analysis Jargons
Before moving to PHPStan, or any other tool, we need to be familiar with the terms that are commonly used in the code analyzation world. You can also think of them as what the tools will be looking for in your software.

###Measurements
**- Naming**
Checking if variables and methods’ names, are they too short or too long? Do they follow a naming convention like camel-case?

**- Type Hinting**
Some tools can suggest a name consistent with the return type. For example a `getFoo()` method that returns a `boolean` is better be named `isFoo()`.

**- Lines of Code**
Measures the line of codes in your class or method against a maximum value. In addition to the number of method's parameter or class' number of public methods and properties.

**- Commented Code**
Most of the time no commented out block of code will be allowed,  as long as you are using a version control system, you can remove unused code and if needed, it's recoverable.

**- Return Statements**
How many return statements do you have through out your method? Many return statements make it difficult to understand the method.

**- Return Types**
Makes sure that return type matches the expected. Having many return types possibilities confuses the analyzers.

###Code Structure

**- Dedicated Exceptions**
Ensure the use of dedicated exceptions, instead of generic run-time exceptions, that can be cached by client code.

**- No Static Calls**
Avoid using static calls in your code and instead use dependency injection. Factory methods is the only exception.

**- DRY**
Checks for code duplication either in repeating literal values or whole blocks of code.

###Complexity

Having a lot of control structures in one method AKA the pyramid of doom. Possible fixes include:
- Early return statements
- Merging nested if statements in combination with helper functions that make the condition readable

###Security Issues

**- Cipher Algorithms**
Ensure the use cryptographic systems resistant to cryptanalysis, which are not vulnerable to well-known attacks like brute force attacks for example.

**- Cookies**
Always create sensitive cookies with the “secure” flag so it’s not sent over an unencrypted HTTP request.

**- Dynamic Execution**
Some APIs allow the execution of dynamic code by providing it as strings at runtime. Most of the time their use is frowned upon as they also increase the risk of code injection.

---
##What Does _PHPStan_ Bring?
###Running _PHPStan_ for the First Time
I have been using _SonarQube_ for quite long time but when I first came across _PHPStan_ [repo](https://github.com/phpstan/phpstan) I found this arguable claim...
> _PHPStan_ focuses on finding errors in your code without actually running it. It catches whole classes of bugs even before you write tests for the code. It moves PHP closer to compiled languages in the sense that the correctness of each line of the code can be checked before you run the actual line.
So to put it to the test, I installed _PHPStan_ in an application that has been analyzed with _SonarQube_ since day one and the results were impressive

![Image Test results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ptzmjgtlmnfdd01rbjfe.png)
_PHPStan_ has many rule levels, and as you can see, the higher the level we select the more errors we get with a maximum of 516 errors. Now the question is, which level should we select? Well first we need to know what are the rules of each level.

###Rule Levels
| Level | Name | Details |
|:---:|:---:|----|
| 00 | Basic Checks | Checks for Unknown classes, unknown functions, unknown methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables. |
| 01 | `$this` Unknowns | Possibly undefined variables, unknown magic methods and properties on classes with `__call` and `__get`. |
| 02 | Methods | Unknown methods checked on all expressions (not just `$this`), validating *PHPDocs.* |
| 03 | Types | Checks for return types, types assigned to properties. |
| 04 | Dead Code | Basic dead code checking - always false `instanceof` and other type checks, dead `else` branches, unreachable code after `return`; etc. |
| 05 | Arguments | Checking types of arguments passed to methods and functions. |
| 06 | Type Hints | Reports missing type hints. |
| 07 | Union Types | Reports 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. |
| 08 | Nullable Types | Report calling methods and accessing properties on nullable types. |
| 09 | Mixed Type | Be very strict about the mixed type, the only allowed operation you can do with it is to pass it to another mixed. |

----
## How to Use _PHPStan_?

### Installation

To use _PHPStan_ with Laravel we are going to use [Larastan](https://github.com/nunomaduro/larastan) extension. [Extensions](https://phpstan.org/user-guide/extension-library#installing-extensions) are useful when there are subjective bugs or to accommodate to a certain framework while taking advantage of _PHPStan_ capabilities.


1. First we install the package with composer

   ```bash
   composer require nunomaduro/larastan:^2.0 --dev
   ```

> Note: This version requires PHP 8.0+ and Laravel 9.0+

2. Then you can start analyzing your code with the console command using the default configuration of _PHPStan_

   ```bash
   ./vendor/bin/phpstan analyse app --memory-limit=25
   ```

   Here we specified the path that we want to analyze `app` and the memory limit `25` MB. You can find all the options of the command line [here](https://phpstan.org/user-guide/command-line-usage).

### Configuration File

_PHPStan_ uses configuration file, `phpstan.neon` or `phpstan.neon.dist`, that allows you to:

* Define the paths that will be analyzed.
* Set the rule level.
* Exclude paths.
* Include _PHPStan_ extensions.
* Ignore errors.
* Define the maximum number of parallel processes

Here is an example of a simple configuration file which by default lives in the root directory of your application but you can learn more from the configuration [reference](https://phpstan.org/config-reference).

```yaml
includes:
    - ./vendor/nunomaduro/larastan/extension.neon

parameters:

    paths:
        - app
        - config
        - database
        - routes

    # The level 9 is the highest level
    level: 5

    ignoreErrors:
        - '#PHPDoc tag @var#'

    parallel:
        maximumNumberOfProcesses: 4

    noUnnecessaryCollectionCall: false
    checkMissingIterableValueType: false
```

### Ignoring Errors

Most probably, you are going to need to ignore some errors which is luckily allowed in two different ways:


1. Inline using *PHPDoc* tags

   ```php
   function () {
       /** @phpstan-ignore-next-line */
       echo $foo;
   
       echo $bar /** @phpstan-ignore-line */
   }
   ```
2. From the configuration file and this is actually more clean

   ```yaml
     parameters:
   
         ignoreErrors:
         
             -
                 message: 'Access to an  undefined property [a-zA-Z0-9\_]+::\$foo'
                 path: some/dir/someFile.php
             -
                 message: '#Call to an undefined method [a-zA-Z0-9\_]+::doFoo()#'
                 path: other/dir/DifferentFile.php
                 count: 2 # optional, and it will ignore the first two occurances of the error
   	      -
                 message: '#Call to an undefined method [a-zA-Z0-9\_]+::doBar()#'
                 paths:
                     - some/dir/*
                     - other/dir/*
   ```

### The Baseline

Introducing _PHPStan_ to the CI pipeline, increasing strictness level or upgrading to a newer version can be overwhelming. _PHPStan_ allows you to declare the currently reported list of errors as *“the baseline”* and stop reporting them in subsequent runs. It allows you to be interested in violations only in new and changed code.


If you want to export the current list of errors and use it as the baseline, run _PHPStan_ with `--generate-baseline` option

```bash
./vendor/bin/phpstan analyse --memory-limit=25 --generate-base
```


> Note: We dropped the path option from the example in [installation](https://outline.robustastudio.com/doc/laravel-static-code-analysis-with-phpstan-oxyQT0wo3v/edit#h-installation) as it is now being set in the config file.


It will generate the list of errors with the number of occurrences per file and saves it in `phpstan-baseline.neon`. Finally we add the baseline file to our `includes`

```yaml
includes:
    - ./vendor/nunomaduro/larastan/extension.neon
    - phpstan-baseline.neon
```

### Adding _PHPStan_ to Your CI/CD

Adding _PHPStan_ to the CI/CD pipeline and running it regularly on merge requests and main branches will increase your code quality. In addition to helping in code review.

Embed this snippet in your `gitlab-ci.yml` file and a code quality report will be generated for any merge request, changes to develop or master branches in addition to release and hotfix branches.

```yaml
stages: 
  - staticanalysis - 

phpstan:
  stage: staticanalysis
  image: composer
  before_script:
    - composer require nunomaduro/larastan:1.0.2 --dev --no-plugins --no-interaction --no-scripts --prefer-dist --ignore-platform-reqs
  script:
    - vendor/bin/phpstan analyse --no-progress --error-format gitlab > phpstan.json
  cache:
    key: ${CI_COMMIT_REF_SLUG}-composer-larastan
    paths:
      - vendor/
  artifacts:
    when: always
    reports:
      codequality: phpstan.json
  allow_failure: true
  dependencies:
    - php-cs-fixer
  needs: 
    - php-cs-fixer
  only:
    - merge_requests
    - master
    - develop
    - /^release\/[0-9]+.[0-9]+.[0-9]$/
    - /^hotfix\/[0-9]+.[0-9]+.[0-9]$/
```

---
### Limitations of Static Analysis

Static analysis is not a substitute for testing, they are different tools targeting different domains in the development lifecycle, and it has some limitation:

* No Understanding of Developer Intent

  ```php
  function calculateArea(int $length, int $width): int
  {
      $area = $length + $width;
  
      return;
  }
  ```

  In the above function, a static code analysis tool might detect that the returned value does not match the defined type  but it cannot determine that the function is semantically wrong!
* Some rules are subjective depending on the context.
* False Positives and False Negatives.

---
### Helpful Links

* [Laravel-specific rules](https://github.com/nunomaduro/larastan/blob/master/docs/rules.md#NoUnnecessaryCollectionCall)
* [Errors to ignore](https://github.com/nunomaduro/larastan/blob/master/docs/errors-to-ignore.md)


* *[PHPDocs](https://phpstan.org/writing-php-code/phpdocs-basics)*[ usage with ](https://phpstan.org/writing-php-code/phpdocs-basics)*[PHPStan](https://phpstan.org/writing-php-code/phpdocs-basics)*
* [List of analysis tools for different languages](https://github.com/analysis-tools-dev)