I introduced "code quality" tools in a later stage of this project (Tango App), mostly because it started as a prototype project, and I was more excited about building something rather than playing around with tools. But also because I am lazy, and this was not something that was presenting me a challenge. I knew that, at some point, I would add it anyway, and the only difference would be that those tools would fix the mess later rather than sooner. I can not say it was a bad decision, however, if this was a team project, I would insist on having them from the start.

But first things first, I had to choose the right tools.

Static analysis

For static analysis, I chose PhpStan, although there are other tools available, like Psalm. I had a good experience with it over the years on various projects, so I thought it is quite suitable.

Installation and setup

Installation was quite straight forward, I just installed it using the composer:

$ composer require --dev phpstan/phpstan

However, after running the first analysis, there was an issue: "Class PHPUnit\Framework\TestCase not found."

My test files are extending the PHPUnit TestCase class, but PhpStan could not autoload this class, and for a good reason. In newer versions of Symfony, the PHPUnit version is defined in phpunit.xml.dist, and this version is downloaded when you run ./bin/phpunit, and installed in ./bin/.phpunit/, therefore it is not included in project's ./vendor/autoload.php.

There are a couple of ways to fix this, and I decided to use the following solution: In phpstan.neon configuration I added PHPUnit's autoload.php file as an additional bootstrap file to be loaded when PhpStan is executed.

The phpstan.neon file then looked like this:

# phpstan.neon

parameters:
    bootstrapFiles:
        - ./bin/.phpunit/phpunit-8.3-0/vendor/autoload.php

After that, the PhpStan was good to go.

Analysing the code base

Since this was already an existing project with more than 100 classes and many more lines of code, I began analysis gradually, starting from level 1. For those who are not familiar with the concept of levels in PhpStan, there are 9 levels, 0 being the loosest and 8 being the strictest, so you can choose what to be checked depending on the state of your project. This is very handy when you are dealing with a large codebase that didn't use static analysis tool(s) so far, and suddenly you have to deal with a lot of problems at once, so at least you can fix those problems one at a time, level by level.

To start level 1 analysis, I simply ran:

$ vendor/bin/phpstan analyse src tests --level 1

After fixing the errors, I moved to level 2, and so on until I reached level 6. For the moment, this is more than enough for me, and I will continue with raising the bar after I switch to PHP 7.4.

Code sniffer

Code sniffer is a tool that helps you detect violations of coding standard rules and also helps you fix them automatically. There are 2 projects available: the squizlabs/PHP_CodeSniffer and FriendsOfPHP/PHP-CS-Fixer. Both are very well maintained projects with lots of activity and they are kind of doing the same thing, although if you start digging deeper you can find some comparisons explaining that there are 2 different ideas behind these tools; PHP Code Sniffer was created with "linting" approach while PHP CS Fixer was created with "fixer" approach in mind. Both have lots of rules, same and different (https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues/3459#issuecomment-359234259). Some people are even using both in a project although there might be some issues.

In the end I had to decide which one to use here, and I went with PHP-CS-Fixer having in mind that if I find it not satisfactory I can always switch or add another one.

Installation

There is more than one way to do it, I added it to my Dockerfile so it's being installed on image build:

# Dockerfile

RUN curl -L https://cs.symfony.com/download/php-cs-fixer-v2.phar -o php-cs-fixer \
    && chmod a+x php-cs-fixer \
    && mv php-cs-fixer /usr/local/bin/php-cs-fixer

This downloads the php-cs-fixer PHAR package, makes it executable, and moves it to the local bin folder so that it available to use.

After rebuilding the image and entering the container I could now use it with:

$ php-cs-fixer -V

which confirmed that it was installed successfully.

Rules

After installation, I had to set up some rules to make it more useful for my project. The great thing about php-cs-fixer is the online available configurator (https://mlocati.github.io/php-cs-fixer-configurator/#version:2.17|configurator). It's a great place to see all the rules that are available along with detailed descriptions and examples, but what's even greater is that I was able to choose from predefined presets.

After choosing the rules (and/or presets) the result was downloaded .php_cs.dist file usable by php-cs-fixer. Here is the example how it looks like:

// .php_cs.dist

<?php
/*
 * This document has been generated with
 * https://mlocati.github.io/php-cs-fixer-configurator/#version:2.17.3|configurator
 * you can change this configuration by importing this file.
 */
return PhpCsFixer\Config::create()
    ->setRiskyAllowed(true)
    ->setRules([
        // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one.
        'align_multiline_comment' => true,
        // Each element of an array must be indented exactly once.
        'array_indentation' => true,
        // Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`.
        'array_push' => true,
        // PHP arrays should be declared using the configured syntax.
        'array_syntax' => ['syntax'=>'short'],

                ...

                // Write conditions in Yoda style (`true`), non-Yoda style (`['equal' => false, 'identical' => false, 'less_and_greater' => false]`) or ignore those conditions (`null`) based on configuration.
        'yoda_style' => false,
    ])
    ->setFinder(PhpCsFixer\Finder::create()
        ->exclude('vendor')
        ->in(__DIR__)
    )
;

Finally running the fixer on my /src folder resulted in a lot of files having code style corrected and unified. Also, because of the fact that at this point I had a pretty good test coverage, I was confident that those fixes did not introduce unwanted changes and no functionality was broken.