Static Analysis (Part 3): The wrap-up

In part 1 of the series we started out with code style tools for the PHP backend as well as the Vue.js frontend.

In part 2 we looked at a number of PHP code quality tools.

Part 3 completes the series. This time we’ll have:

  • a couple of tools focused on very specific areas of the code
  • a different look at the topic
  • an outlook on future topics

We also look at one of the downsides of using all these tools. As before, we’ll be using the application from the SPA tutorial.

Special purpose tools

We start with two very small and focused tools:

  • PHPMND – PHP Magic Number Detector
  • PHPCPD – PHP Copy Paste Detector

PHPMND

phpmnd is a tool that aims to help you to detect magic numbers in your PHP code.

https://github.com/povils/phpmnd

What are magic numbers? PHPMND’s github page has the following definition:

A magic number is a numeric literal that is not defined as a constant, but which may change at a later stage, and therefore can be hard to update. It’s considered a bad programming practice to use numbers directly in any source code without an explanation. In most cases this makes programs harder to read, understand, and maintain.

https://github.com/povils/phpmnd

Instead of magic numbers use constants. They give meaning to those numbers. They are much easier to change. You also get auto-complete to avoid typos. Simple as that.

Setup and usage

We install PHPMND as a dev dependecy because it is not available as PHAR file (see part 1 of the series for a discussion about the different ways to set up the tools):

php composer.phar require povils/phpmnd --dev

This installs a script at /vendor/bin/phpmnd which we can point at the /src directory like this:

vendor/bin/phpmnd src/ 

This doesn’t find any magic numbers within our code. That is because the defaults of the tool are rather loose. So let’s use all so-called extensions and run the tool again:

vendor/bin/phpmnd src/ --extensions=all
phpmnd 2.3.0 by Povilas Susinskas


--------------------------------------------------------------------------------

Controller/MovieController.php:47  Magic number: 201
  > 47|         return $response->withStatus(201, sprintf('Movie id %s created', $movie->getId()));

--------------------------------------------------------------------------------

Total of Magic Numbers: 1
Time: 00:00.170, Memory: 10.00 MB

PHPMND detected one magic number: the 201 status code we return after adding a new movie. Let’s use a constant from the aptly titled StatusCodeInterface which is part of the PSR Http Message Utils package.

public function addMovie(Request $request, Response $response): Response
{
    $body = $request->getParsedBody();
    $movie = $this->addMovieCommand->execute($body['title'], $body['description']);

    return $response->withStatus(StatusCodeInterface::STATUS_CREATED, sprintf('Movie id %s created', $movie->getId()));
}

This fixes the error.

We could stop at this point, but PHPMND can do more. It can also look for string literals by using the --strings option:

 vendor/bin/phpmnd src/ --extensions=all --strings
phpmnd 2.3.0 by Povilas Susinskas


--------------------------------------------------------------------------------

Controller/IndexController.php:27  Magic number: index.html
  > 27|         return $this->view->render($response, 'index.html');

--------------------------------------------------------------------------------

Controller/MovieController.php:45  Magic number: title
  > 45|         $movie = $this->addMovieCommand->execute($body['title'], $body['description']);

Controller/MovieController.php:45  Magic number: description
  > 45|         $movie = $this->addMovieCommand->execute($body['title'], $body['description']);

Controller/MovieController.php:47  Magic number: Movie id %s created
  > 47|         return $response->withStatus(StatusCodeInterface::STATUS_CREATED, sprintf('Movie id %s created', $mo
vie->getId()));

Controller/MovieController.php:64  Magic number: Content-Type
  > 64|         return $response->withHeader('Content-Type', 'application/json');

Controller/MovieController.php:64  Magic number: application/json
  > 64|         return $response->withHeader('Content-Type', 'application/json');

--------------------------------------------------------------------------------

Core/Domain/Entity/Movie.php:66  Magic number: id
  > 66|             'id' => $this->getId(),

Core/Domain/Entity/Movie.php:67  Magic number: title
  > 67|             'title' => $this->getTitle(),

Core/Domain/Entity/Movie.php:68  Magic number: description
  > 68|             'description' => $this->getDescription(),

--------------------------------------------------------------------------------

Core/Infrastructure/Repository/MovieRepository.php:18  Magic number: /var/www/html/movies.json
  > 18|     private $path = '/var/www/html/movies.json';

Core/Infrastructure/Repository/MovieRepository.php:54  Magic number: id
  > 54|                 $this->movies[$item['id']] = new Movie(

Core/Infrastructure/Repository/MovieRepository.php:55  Magic number: id
  > 55|                     $item['id'],

Core/Infrastructure/Repository/MovieRepository.php:56  Magic number: title
  > 56|                     $item['title'],

Core/Infrastructure/Repository/MovieRepository.php:57  Magic number: description
  > 57|                     $item['description']

--------------------------------------------------------------------------------

Total of Magic Numbers: 14
Time: 00:00.164, Memory: 10.00 MB

This gives us lots of errors. Basically every string used within the application is reported here. We could fix this the same way as before: by introducing constants.

Summary

Magic numbers can be quite annoying, especially as it’s easy to get rid of them. Using constants for every string, though, can feel really tedious. So I would probably not use this as a mandatory check but only run it every now and then. For example, it is a nice reminder that relying heavily on associative arrays should be avoided as you never really know what you are dealing with.

PHPCPD

phpcpd is a Copy/Paste Detector (CPD) for PHP code.

https://github.com/sebastianbergmann/phpcpd

PHPCPD is a tool by Sebastian Bergmann, the creator of PHPUnit. It analyzes the code to find sections which have been copy-pasted or more generally speaking, contain duplicated code.

Setup and usage

We install PHPCPD using a PHAR file:

wget https://phar.phpunit.de/phpcpd.phar

We than point it at the /src directory:

php phpcpd.phar src/
phpcpd 6.0.3 by Sebastian Bergmann.

No clones found.

Time: 00:00.011, Memory: 2.00 MB

The tool didn’t find any duplicated code in our application. Which is not surprising as it is very small. But for real life applications this can be a really useful check.

Summary

PHPCPD is a nice little tool which can help you to find areas containing duplicated code. These are candidates for future refactorings which can help to simplify the code.

BTW: PHPStorm has a similar feature which can be used to find duplicated code right from within the IDE.

PHPUnit and CRAP

I’m not going to look at PHPUnit in general but only at one of the many things it can do: the CRAP metric. The CRAP metric is part of the code coverage report PHPUnit can generate. The code coverage report analyzes how much of the code is covered by unit tests. The CRAP metric combines this with the cyclomatic complexity of the code (we talked about cyclomatic complexity when looking at PHPMD).

CRAP stands for Change Risk Anti-Patterns, well, officially at least. A high CRAP index means that the code is very complex and not many (if any) unit test cover it. This means there is a high risk of bugs which can go undetected. To reduce the CRAP index you can either refactor the code to reduce its complexity or you can write more tests. Preferably both.

The CRAP index is really useful in telling you which part of the code needs more unit tests. Instead of focusing on increasing the overall code coverage focus on adding tests where they provide the greatest benefit. That’s the code with the highest CRAP index.

Setup and Usage

We install PHPUnit by downloading the PHAR:

wget https://phar.phpunit.de/phpunit-9.phar -O phpunit.phar

We can then point the tool at our tests. Actually, we haven’t added any tests yet, but let’s just pretend there were tests and run the tool anyway. That’s OK, as we are only interested in the CRAP metric.

mkdir tests
php phpunit.phar tests --coverage-html=coverage --whitelist=src/
PHPUnit 9.5.2 by Sebastian Bergmann and contributors.

No tests executed!

Generating code coverage report in HTML format ... done [00:00.410]

This generates the code coverage report at coverage/index.html which looks like this:

Code coverage report

The first half of the page looks at the code based on the classes, the second half looks on the methods. They both contain a section “Project Risks” which contain all classes or methods with a high CRAP metric. Clicking on either one opens the report for the MovieRepository.

Coverage MovieRepository

The class has an overall score of 56, the readFile method a score of 12. Which suggests that this a rather complex and not adequately covered by unit tests.

Summary

As PHPUnit and code coverage reports are (or should be) part of any modern PHP project, take a look at the CRAP metric every now and then. It’s a great indicator for classes that would benefit from a refactoring and more unit tests.

Conclusion and outlook

That’s the end of the series on static analysis. We started with code style tools for the PHP backend as well as the Vue.js frontend. In part 2 we had an in-depth look at the backend provided by a selection of code quality tools. In this final part we wrapped things up with some more tools and a different look at PHPUnit.

I really like the insights gained from all of the tools, even if I don’t agree with all of the rules they suggest. But that’s what all the rules are: suggestions to improve the code. Don’t try to go all-in on an existing project. Don’t set up all the tools as mandatory checks in the CI process. Instead come up with some basic standards or use the baseline feature provided by some tools and work your way up from there. Add other tools to only generate reports but don’t let them fail the build.

There are many more tools out there. I actually wanted to include PhpMetrics into the series but currently it’s documentation is rather incomplete and buggy which makes it hard to use. Another very interesting tool is Deptrac. But that will be another article.

You can find the current version of the code in the repository.