Improve quality of PHP applications with static analysis and SonarQube

Jakub Nowak
Byborg Engineering
Published in
7 min readJan 17, 2022

--

Have you ever worked on Microservices architecture with a lots of services where every one of them is created by different team and with different code standards? Situations like that are never ideal, but sometimes there’s no other choice, and you need to improve what you can. This was the exact problem we encountered.

We decided to unify the code and improve its quality company wide, and we needed to find a suitable set of tools that could help with this task. When deciding which tools to use, we had to consider:

  1. We wanted to compare the code quality of multiple applications fast.
  2. Because every application is maintained by different developers, we wanted to compare them automatically without the maintainers’ help.
  3. We wanted to measure the progress of the code’s quality over time

Static program analysis is the analysis of computer software that is performed without actually executing programs

Wikipedia

After testing multiple tools we decided to use SonarQube, which gathers data for multiple applications and allows to compare them easily.

SonarQube is an open-source platform developed by SonarSource for continuous inspection of code quality to perform automatic reviews with static analysis of code to detect bugs, code smells, and security vulnerabilities on 20+ programming languages. SonarQube offers reports on duplicated code, coding standards, unit tests, code coverage, code complexity, comments, bugs, and security vulnerabilities.

Wikipedia

Are you interested in knowing more? Let’s see what we did.

Prerequisites

The easiest way to test all the tools is to use Docker. Some scripts are in PHP, so it’s recommended to have at least version 7.0 of PHP installed. All commands were tested on MacOS with Docker version 20.10.7. Because we will run multiple containers which will have to communicate, we must create custom network:

docker network create mynetwork

We also need some code to analyse. You can use any of your personal projects, or this example repository I created and use in all examples below.

Run SonarQube locally

First thing: we need to start SonarQube. SonarQube will gather all the data about our applications.

docker run \
--network=mynetwork \
--name=sonarqube \
-p 9000:9000 \
-d \
sonarqube:9.2.1-community

You can verify if the SonarQube container is running properly once the command finishes.

docker ps
Running SonarQube docker container with exposed port 9000

Now, open http://localhost:9000 in your browser and you should see the login page to SonarQube GUI. The Default credentials are “admin : admin”.

SonarQube login form — for first login use admin:admin

The first time you log in, you will be prompted to change your password.

The next thing we need is to generate SonarQube token. This token will be used by SonarScanner to send data to SonarQube. Go to My Account > Security and generate your token. Don’t forget to store generated token, we will need it later.

Generation of SonarQube token

Analyze the code

First, we need to clone the repository we want to analyze. Don’t forget to change your working directory to the root of the cloned repository.

git clone git@github.com:nowaja/sonarqube-example.git
cd sonarqube-example

We are now ready to run the static analysis tools. Every tool generates a json report in the ./test-reports directory. We will import these reports to SonarQube later.

If you need to know more about thetools’ configuration, you can check official documentations for PHPStan, Psalm and Deptrac.

docker run --rm -v $(pwd):/app nowaja/deptrac:0.15.2 analyze --formatter=json --json-dump=test-reports/deptrac-report.json /app/depfile.yamldocker run --rm -v $(pwd):/app nowaja/psalm:4.15.0 --output-format=sonarqube --report=test-reports/sonarqube.jsondocker run --rm -v $(pwd):/app ghcr.io/phpstan/phpstan:0.12.89 analyse -c phpstan.neon --error-format=json > test-reports/phpstan-report.json

When you check the files that are generated in the ./test-reports directory, you can see that every one of them is in a different format. Importing PHPStan (in json output format) and Psalm (in sonarqube output format) report files to SonarQube has been supported since version 9.1. However, Deptrac’s (and lots of other tools’) report file format is not supported by SonarQube, so we need to make a little adjustment.

For these cases, there’s a SonarQube feature called Generic Issue Import. It supports precisely defined json format that can be imported with custom issues. So the only thing we need to do is to convert the Deptrac report file to a format supported by SonarQube. You can use this transform script.

bin/transform-deptrac-results-for-sonarqube test-reports/deptrac-report.json

If you’re using a version of SonarQube that does not support importing PHPStan report files, you can convert them to a generic issue format with this script. Psalm sonarqube output reports are automatically generated in a generic issue format, so it works flawlessly even with older SonarQube versions.

bin/transform-phpstan-results-for-sonarqube test-reports/phpstan-report.json

Now, all our files are ready and we can proceed to the last step.

Time for the real magic!

We need to run SonarScanner, but how does it work? By loading sonar-project.properties config file and analyzing the code based on those configuration values. It also run default Quality profile analysis for a given language. If you need one, you can find a sample sonar-project.properties file here.

sonar.projectName=SonarQube Example
sonar.projectKey=sonarqube-example
sonar.language=php
sonar.sources=src
sonar.php.phpstan.reportPaths=test-reports/phpstan-report.json
sonar.php.psalm.reportPaths=test-reports/sonarqube.json
sonar.externalIssuesReportPaths=test-reports/deptrac-report.json

For older SonarQube versions, you can use this sonar-project.properties file:

sonar.projectName=SonarQube Example
sonar.projectKey=sonarqube-example
sonar.language=php
sonar.sources=src
sonar.externalIssuesReportPaths=test-reports/deptrac-report.json,test-reports/phpstan-report.json,test-reports/sonarqube.json

Do you remember the token we generated in the first step? Now is the time to finally use it! We have to pass the token to SonarScanner to authenticate SonarQube.

docker run --rm \
--network=mynetwork \
-e SONAR_HOST_URL="http://sonarqube:9000" \
-e SONAR_LOGIN="{TOKEN}" \
-v "$(pwd):/app" \
sonarsource/sonar-scanner-cli:4.6 \
-D sonar.projectBaseDir=/app

Don’t forget to replace {TOKEN} with the previously generated SonarQube token. Please be aware of two things when running SonarScanner: we use sonarqube:9000 as host url, not localhost:9000. This is because the containers are on the same network and they communicate with each other using their names, not the host machine. The other thing is the proper configuration of sonar.projectBaseDir. This is because of the PHPStan report, which automatically generates absolute paths for all files. Those files are located in the /app folder during PHPStan analysis.

After SonarScanner finishes its job (which may take some time), you can find the newly created project in SonarQube.

Project crated after first SonarScanner analysis

And naturally, you will see our imported issues.

Project detail with imported issues

There are a lot of options of what to do next. SonarQube has got lots of built-in features that can help you get rid of reported issues. If you have the same users in SonarQube as in git, you can automatically assign issues to users based on the commit authors, and it can be integrated with your version control system and automatically block your PRs from merging if any new issues are introduced (see Quality Gates). It can also show you graphs about how your code has changed over time, or check test coverage percentage and more. SonarQube also provides an API, so you can fetch report data and use them in any way that meets your needs. If you need to motivate more developers to continuously fix issues, you can even add gamification elements with Quboo.io integration.

Now, you know how to automatically analyze a single application and check the results in SonarQube. However, this was not enough for our use case, so we had to create a script that analyzes multiple repositories.

We need to take a few simple steps with every repository:

  1. Clone the repository master.
  2. Remove existing configurations and baseline files for every tool we want to use. This is mandatory, because every application may already include static analysis tools with different configurations. We need to run all analysis with the same configurations, otherwise the comparison would be useless.
  3. Copy our default configs to repository.
  4. Run the analysis mentioned above, and convert the reports to SonarQube’s format and run SonarScanner.

You can check the sample shell script in the example repository. It can be easily adapted to your git namespace by editing lines 12 and 13. This script gets the list of repositories (can also be loaded from API), loops through them, and processes every repository with the steps above. By default, it runs two parallel analysis, which can be adjusted on line 14. After the script finishes, you can find the results in SonarQube.

If you want to generate reports from all projects via the SonarQube API, you can use the example script (don’t forget to replace {TOKEN} with your SonarQube token):

bin/generate-sonarqube-report http://localhost:9000 $(echo -n '{TOKEN}:' | base64)

This script generates sonarqube_report.html file, which contains info about all the projects. You can check the script to understand SonarQube API more.

After using that data to compare multiple applications, we now have some insight as to the applications quality and we can decide which applications we should start refactoring/improving first. After all, that was our ultimate goal before starting all of this.

--

--