Using PHPStan with Symfony - static analysis for better PHP code quality
Tags: php, programming, codequality, symfony
Published: 2022-03-14 07:49:03


Software developers are not robots, even after seven years of PHP programming experience, I do sometimes make bugs in the code. In the age of smart IDEs, it's getting harder and harder to make silly mistakes, most IDEs catch them very well, but it still happens.

To avoid errors in the code, we can use tools that detect them during **static code analysis**. What kinds of errors are detected by static code analyzers? Lots, such as calls to unknown classes/methods, number of method arguments, using undefined variables, calling methods on nullable types, types of arguments passed to methods, and much much more.

There are many tools for static **PHP code analysis**, but one of the most popular is PHPStan. It is possible that it is due to its ease of use, versatility and the possibility of using many extensions for example to Symfony, doctrine, elasticsearch, monolog, guzzle etc. I'll show you how to easily add PHPStan to the Symfony project.

## How to install PHPStan in a Symfony framework?

It is easier when we create a new environment and have a new, greenfield, clean and fresh project. Then we do not init PHPStan into the "dirty" code, but we have the **code analysed from the beginning**. But of course that rarely happens. If you haven't used static code analysis tools before, you are probably adding it because you already encounter some problems with your code.

First step, install <a rel="nofollow" href="https://packagist.org/packages/phpstan/phpstan" target="_blank">PHPStan</a> via Composer

```shell
composer require --dev phpstan/phpstan
```

It will install PHPStan’s executable in the project `bin` directory. Depending on your [Symfony framework](https://accesto.com/blog/what-is-php-framework-symfony-explained-for-executives/) version/configuration it will be `vendor/bin` or just `bin`.

Second step.. just run the **PHPStan analyse command** (note that it is not a Symfony console command):

```shell
bin/phpstan analyse src
```

where `src` is a directory name you want to check.

That's it, you can start correcting bugs :). If this is a new project, you'll probably see there are no errors… but, let's use this in a project I was working on a while ago, and PHPstan wasn't used in it.

## How to use PHPStan in a Symfony project?

PHPStan can be run with few parameters. One of them is required, this is the directory to be checked.

The `level` parameter is very important - "Specifies the rule level to run.". Analyse command can be run with one from nine levels where **level 0 is the loosest and level 8 is the strictest**. Default level is 0. How do these levels differ? You can check it in <a rel="nofollow" href="https://phpstan.org/user-guide/rule-levels" target="_blank">documentation</a>. If this is a new, empty project, and you are just starting to write code, **it is worth using a higher level**. Then you will maintain high standards from the very beginning. But if you are adding PHPStan to an existing project, it will probably be not easy to pass even the lowest level.

How the response look like? Let’s check the project I mentioned. I will start from default level 0.


![PHPStan analysis results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b0ic2h2ua1pxjgctu7ju.png)

![Found 53 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/50dxulsk8q2pixg77bz3.png)

So in the src directory PHPStan found 53 bugs. What types of errors are these?

* Access to an undefined property $foo
* Method foo() invoked with X parameter, Y required.
* Class \Bar constructor invoked with X parameters, Y required.
* Call to an undefined method bar()
* Method \Bar::foo() should return string but return statement is missing.

Even though 53 seems like a lot, **it is very easy to correct it** because these are simple ones.

So let's try to raise the level, how many bugs will it find? I do not have good news:

level 2 - 629 errors
![Found 629 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ib5wl1kuiqva3trcrg4j.png)

level 8 - 2695 errors
![Found 2695 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pys45xgytjooe1lgphw5.png)

No customer will agree if you tell him, now for a few weeks I will correct all errors found by PHPStan. You need to approach it wisely.

## How to configure PHPStan?

Another parameter in the analyse command is `--configuration` (`-c`). It’s a path to file with configuration in NEON format (very similar to YAML).

What can we configure there? A lot, you can find all the options in the <a rel="nofollow" href="https://github.com/phpstan/phpstan-src/blob/0.12.92/conf/config.neon" target="_blank">documentation</a> but, for me, the most important options are paths to be checked and excluded from check, level and ignoring errors.

When I look at the result for level 2, I can see that most of the errors are `PHPDoc tag @param has invalid value ($foo): Unexpected token "$foo", expected type`. Let's be honest, **this is not a critical bug**, I won't be fixing all PHPDoc tags right now. Let's say I'll fix it another time, and now I'd like to ignore it (but make sure to put it on my refactoring list to keep a track on <a href="https://accesto.com/blog/technical-debt-the-silent-villain-of-web-development/" target="_blank">the technical debt</a>). For this, I'll use the ignore error option in my configuration file. We can use regular expressions there, so I will ignore all errors starting with <i>"PHPDoc tag ..."</i>

```yml
parameters:
    ignoreErrors:
        - '#PHPDoc tag .#'
```

When you add your configuration file, do not forget to add it as a parameter for the analyse command. Otherwise, PHPstan will look for a file with the default name `phpstan.neon`

```shell
bin/phpstan analyse -l 2 -c myPHPStanConfig.neon src
```

![Found 268 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aoply3wf0flm7s1u3e1x.png)

This way the number of bugs has dropped from 629 to 268. **I didn't fix these bugs**, I just silenced them, I will correct them later (yeah, someday ;)) because they are not critical. I allowed myself to silence a few other minor bugs:

```yml
parameters:
    ignoreErrors:
        - '#PHPDoc tag .#'
        - '#. typehint specified#'
        - '#. return statement is missing#'
        - '#Return typehint of method .#'
        - '#Call to an undefined method Psr\\Container\\ContainerInterface::getParameter\(\)#'
        - '#Call to an undefined method Doctrine\\ORM\\EntityRepository<.+>::find.+\(\).#'
```

![Found 108 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyv07fwtk39y9k5glg1o.png)

So right now I have "only" 108 erros to check and fix. But I am still on level 2, how does it look like on level 8?

![Found 833 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yf1o5i2ife4t61fu0zse.png)

833 errors, it’s not good, but that's better than the initial 2695 errors. Fortunately, **this post is not about correcting bugs**.

If this was a legacy project and it is not our priority to correct the legacy files, we can exclude whole legacy directores.

```yml
parameters:
     excludePaths:
        - src/Utils/*
```

With this configuration, PHPStan will skip all files in the `src/Utils/*` directory

## PHPStan extensions

By default, PHPStan does not understand the magic that Symfony and Doctrine do sometimes. We can use framework specific extensions that expand it’s knowledge.

### How to install PHPStan Symfony extension? 

First we need to start with generic <a rel="nofollow" href="https://github.com/phpstan/extension-installer" target="_blank">PHPStan Extension Installer</a>, this is a composer plugin that handles the **automatic installation of new extensions**.

Just run

```bash
composer require --dev phpstan/extension-installer
```

Than install <a rel="nofollow" href="https://packagist.org/packages/phpstan/phpstan-symfony" target="_blank">PHPStan Symfony framework extension</a>:

```bash
composer require --dev phpstan/phpstan-symfony
```

After that, we need to define in the configuration file the path to the file that describes our container. It’s a XML file generated in the `cache/dev` directory. Extension documentation has some example:

```yml
parameters:
    symfony:
        # container_xml_path: var/cache/dev/srcDevDebugProjectContainer.xml
        # or with Symfony 4.2+
        container_xml_path: var/cache/dev/srcApp_KernelDevDebugContainer.xml
        # or with Symfony 5+
        # container_xml_path: var/cache/dev/App_KernelDevDebugContainer.xml
```

### How to install PHPStan Doctrine extension?

PHPStan Doctrine extension provides support for all Doctrine magic. It checks our DQLs, QueryBuilders, validates entities and relations, recognizes magic methods like `findBy*` etc.
We already have "PHPStan Extension Installer", so we just need to install extension:

```shell
composer require --dev phpstan/phpstan-doctrine
```

To use more advanced analysis like DQL validation we **have to provide an object manager**. It’s described in extension <a rel="nofollow" href="https://packagist.org/packages/phpstan/phpstan-doctrine" target="_blank">documentation</a>. We need to create a file eg. `tests/object-manager.php`

```php
use App\Kernel;
 
require __DIR__ . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();
```

and use it in PHPStan configuration:

```yml
parameters:
	doctrine:
		objectManagerLoader: tests/object-manager.php
```

So let's check my project after adding these extensions.

For level 2:

![Found 429 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lyoict12iczhcux1mi0x.png)

For level 8:

![Found 1166 errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qrvv8uu1y8cszc142m5a.png)

The number of bugs found increased for level 2 from 108 to 429, and for level 8 from 833 to 1166 errors. As you can see, **adding extensions improves code validation efficiency**.

### Other PHPStan extensions

There are many other extensions, for example you can check **<a rel="nofollow" href="https://packagist.org/packages/phpstan/phpstan-strict-rules" target="_blank">PHPStan strict rules</a>** extension. From their documentation:

<blockquote>
This repository contains additional rules that revolve around strictly and strongly typed code with no loose casting for those who want additional safety in extremely defensive programming
</blockquote>

And that's exactly how it works. I added it to my project. The number of errors detected increased on level 2 from 429 to 684 and on level 8 from 1166 to 1459.

## How to automate PHPStan in a Symfony project?

Of course, even if you start with a clean project and you have zero errors, if you had to make sure that you **run PHPStan before each Commit**, you would forget one time. Someone else might not use it at all and with time the number of errors would grow.

From time to time you would have to sit and fix all bugs. This is not good, the customer will not want to pay for it.

Therefore, **PHPStan should be added to your CI** (you probably have some?). You can add the PHPStan check run job to composer scripts or directly to CI configuration. If it detects an error, then the build will fail, and you will know with each push that you need to correct something.

It is worth thinking about it from the beginning of the project, already at the stage of the initial setup and dockerization of the Symfony project, which I described in the previous post: <a href="https://accesto.com/blog/simple-docker-setup-for-symfony-project/" target="_blank">Simple Docker setup for Symfony project</a>

## Conclusions

Is it worth installing PHPStan in an existing project? Of course, but.. probably not at the highest level from the beginning. You will have to start at the lowest level and work your way up gradually.I also recommend using extensions that only **increase PHPStan capabilities**, and thus find more potential bugs.

PHPStan is a great tool that will help you to maintain better quality of your PHP code. But remember, neither PHPStan nor any otehr tool will substitute the proper design (eg. following <a href="https://accesto.com/blog/solid-php-solid-principles-in-php/" target="_blank">SOLID in PHP</a>), practices (like a <a href="https://accesto.com/blog/Boy-scout-rule-in-6-examples-the-basic-principle-of-web-development/" target="_blank">Boy Scout rule</a>) and simply a thoughtfull development. 

If you are not coping with your code, because you have not started using PHPStan on time, you can contact us, **we deal with difficult cases**.