Maintaining a code base can be a great challenge. It often gets worse the older the code is. If there is little to no test coverage, adding new code is always a bit of a risk and any bugfix might introduce new issues. Static code analysis does not solve those problems for good but it helps prevent avoidable mistakes and inevitable leads to an improved code quality and helps to achieve fully and strict typed applications. That's why today we will look at two static code analysis tools: PHPStan and php-cs-fixer and see how an integration into a TYPO3 project could look like.
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.
PHPStan can be easily installed with composer as a development requirement:
composer require --dev phpstan/phpstan
This has been done for the TYPO3 core in January 2020 (Patch) and shortly after that PHPStan was added to the automated bamboo builds for pre merge testing as well as for the nightly test runs (extending the integrity checks summarized by Christian Kuhn in this article). So if the core is doing it, we want it in our projects as well, right? So how does it work?
PHPStan can be executed from the command line and will scan each PHP file in a given directory for compliance with it's rule set.
vendor/bin/phpstan analyse path/to/my/code
PHPStan has a huge set of rules that are grouped into levels from 0 to 8 with 8 being the strictest and usually what we are aiming for to get green. We add a configuration file named
phpstan.neon to our project to tell PHPStan what level it should apply. We can also override other configuration (e.g. disable or enable feature flags). As a PHPStan level is defined by a set of rules in a neon file we will find one file for each level (see at GitHub). We also see that each level includes the next lower level, so we only need to specify our maximum level as a parameter and all lower levels are included automatically.
parameters: level: 8 paths: - path/to/my/code - path/to/my/other/code
In this example
phpstan.neon we also specify the paths that we want PHPStan to analyse, so we do not need to add the paths to the command. If we always want to use the highest level even if PHPStan adds a new one, we can use the alias
max in the configuration file instead of the levels number.
We also can exlude errors that we know are no real issues but PHPstan will report nevertheless:
parameters: ignoreErrors: - '#Variable \$_EXTKEY might not be defined\.#'
Introducing PHPStan in a legacy project with lots and lots of errors can be overwhelming and also discouraging at first sight. But there is a simple solution that allows to introduce PHPStan nevertheless, declare the current code base as "given" and at least have PHPStan analyze new code. For this a so called baseline file can be generated that simply includes all errors as ignore rules so that PHPStan will report no errors:
vendor/bin/phpstan analyze --generate-baseline
Of course PHPStan allows for further customization and is extendible. Let's look at that next.
In case that framework specific behavior needs to be respected by PHPStan, it can be extended. For TYPO3 Sascha Egerer (say thanks at his Twitter) has provided such a package with
phpstan-typo3 (GitHub, Packagist). It contains class reflection for e.g. the "magic" extbase repository methods
findBy<Property> etc. so that PHPStan can handle those. The package also adds support for
ObjectStorage as well as handling many TYPO3 specific constants.
To use the package we can just require it via composer and include the
extension.neon from the extension in our own
composer require saschaegerer/phpstan-typo3 --dev
There also is the package
phpstan/extension-installer (GitHub) which will include all PHPStan extensions automatically.
Additionally the package
friendsoftypo3/phpstan-typo3 (GitHub) exists which is exclusively maintained to support the TYPO3 core. Please do not use this extension in your TYPO3 projects, stick to Saschas package instead especially when it comes to pull requests!
With that being said, let's have a quick look at the ongoing efforts to improve the TYPO3 core code with the help of PHPStan.
As mentioned already, PHPStan has been integrated into the TYPO3 core pre-merge and nightly pipelines and we are working constantly on improving the situation step by step. As the TYPO3 core has some quite old components that have been developed a loooong time ago, applying the max level of PHPStan leads to more than 10.000 errors (that is at time of writing of course and will hopefully reduce a lot in the future). As this would have been a way to big baseline file that would also have been very hard and annoying to maintain, the core choose a different approach to get as much as possible out of PHPStan with the current code base while still being able to progress in small steps.
The main driver behind these efforts is Alexander Schnitzler (say "hello" and "thanks" @ his Twitter). Alex came up with the idea to copy the PHPStan level definitions into the core (checkout the
Build folder, for example at GitHub) and disable all rules that are currently being problematic while keeping all other rules from all levels active. This way we can make sure that no new violations of already satisfied rules are introduced while being able to activate more and more rules by patching the core accordingly (have a look at all the merged and currently open PHPStan related patches @ Gerrit).
The workflow looks like this: Once a rule is green it will be activated for the core with a final patch and once a level is done completely the file is removed from core as we no longer need to override specific rules (example patch). We aim to backport as much as possible of these changes to make future backports less conflicting. With this strategy we will be able to improve the code quality of the TYPO3 core in general, making it more reliable and also improve maintainability in the long run. If you are interested in joining this mission, there is a #typo3-phpstan channel at the TYPO3 slack and patches are always happy if they get reviewed and tested.
Finally there is this YouTube video where Alex talks a little bit about PHPStan and does some work on the TYPO3 core to illustrate the challenges. The video is only available in German language.
On a side note: PHPStan integration in PhpStorm will be improved as well (or likely already has been if you read this at a later point in time) with version 2020.3. Susanne Moog (say thanks at Twitter) wrote a blog post about how to integrate PHPStan into PhpStorm.
Besides PHPStan there is the php-cs-fixer to help us maintain a clean code base in terms of CGL. Let's look at that tool as well.
Most developers will probably already know the gem and constant source of happiness that is the "PHP Coding Standards Fixer" aka php-cs-fixer. Of course installable via composer with
$ composer require friendsofphp/php-cs-fixer --dev
The php-cs-fixer is also available through many other means all of which are described in the projects
Readme.md at GitHub. I wont spend too much time on this tool as it is already widely used. I just want to point out how to quickly set it up in a TYPO3 project to use alongside PHPStan in our CI pipelines. The php-cs-fixer can be executed with the command
Note that this will not only report files with CGL violations but it will also make the necessary changes to resolve those violations. The set of desired rules can be configured and so can the path(s) that should be scanned by the tool. php-cs-fixer will respect a config file named
.php_cs.dist. This file contains PHP and is meant to return an instance of
PhpCsFixer\Config. We can configure our own set of the many many rules that the cs-fixer supports like this
return PhpCsFixer\Config::create() ->setRules([ 'array_syntax' => [ 'syntax' => 'short' ], 'binary_operator_spaces' => [ 'align_equals' => false, 'align_double_arrow' => false ], 'cast_spaces' => false, 'combine_consecutive_unsets' => true, 'concat_space' => [ 'spacing' => 'one' ], 'linebreak_after_opening_tag' => true, 'no_blank_lines_after_class_opening' => true, 'no_blank_lines_after_phpdoc' => true, 'no_extra_consecutive_blank_lines' => true, 'no_trailing_comma_in_singleline_array' => true, 'no_whitespace_in_blank_line' => true, 'no_spaces_around_offset' => true, 'no_unused_imports' => true, 'no_useless_else' => true, 'no_useless_return' => true, 'no_whitespace_before_comma_in_array' => true, 'normalize_index_brace' => true, 'phpdoc_indent' => true, 'phpdoc_to_comment' => true, 'phpdoc_trim' => true, 'single_quote' => true, 'ternary_to_null_coalescing' => true, 'method_argument_space' => ['ensure_fully_multiline' => true], 'no_break_comment' => false, 'blank_line_before_statement' => false, ]);
This is only a small subset of the available rules. Please refer to the projects Readme.md for a exhaustive list of rules. To make all our lives easier Benni Mack (say hello @ his Twitter) maintains the small package
typo3/coding-standards (GitHub) that bundles the rules currently applied by the TYPO3 core. If we require this package we can use it in our
If we now also specify what directories to scan we are off to a good start. This is also done in the configuration file:
$finder = PhpCsFixer\Finder::create() ->exclude('node_modules') ->exclude('vendor') ->in('src/extensions') ->in('tests'); return \TYPO3\CodingStandards\CsFixerConfig::create()->setFinder($finder);
And this concludes this short article about two popular code analysis tools that can be used to great benefit in TYPO3 projects and that both have community prepared packages to help with the integration. I hope there was something useful in the article for you. Happy code analyzing!