7 minutes reading time (1465 words)

Test your Extension, Part 3: PHPStan

Test your Extension, Part 3: PHPStan

In this series, we explore methods and tools to test a custom Joomla extension. In this third episode, we’ll use PHPStan, a tool to examine and debug source code before the program is run. You don’t even have to write a test to catch bugs at an early stage.

Static Analysis

In the last two episodes we tested code by running it, and testing if it worked correctly. We compared the results of the executed code with what we expected.

  • In episode one we used PHPUnit to test an isolated piece of software (a “unit”) in a controlled environment, without the extension being installed in a Joomla site.
  • In episode two we used Cypress for end-to-end tests, running the extension in a Joomla site, verifying if it does what we expect it to do.

Static analysis is a different approach: we don’t run the code, but have it reviewed to detect bugs. A bunch of general rules have been formulated, for your code to conform to. Static analysis can for instance check if you:

  • refer to a non-existing class,
  • call a non-existent method,
  • use deprecated code,
  • pass the wrong number of arguments to a function,
  • have type errors.

And if those ready-made rules are not enough, you can even make your own rules. For instance to examine if you correctly used dependency injection.

One of the most used static analysis tools in PHP is PHPStan. You can see it in action in the Joomla core repository to validate the code of pull requests. In the Weblinks repository you can see an example of a setup for an extension.

In short: PHPStan finds bugs before the code is run.

Install, configure and run PHPStan

  1. Composer

    PHPStan is installed via Composer. You don’t want to ship it with your extension, so just like PHPUnit you can put it under the developer requirements in your composer.json. Here is a minimal example:

     "require-dev": {
        "phpstan/phpstan": "^2.1.29"
      },

    Run composer install to install PHPStan.

  2. Configuration

    PHPStan reads settings from a config file (in yaml-format), called “phpstan.neon”. Here is a basic configuration:
     
    parameters:
        level: 5
        paths:
            - src

    The level parameter controls how strict PHPStan is. It ranges from 0 to 10. Level 0 reports obvious errors like undefined variables. Level 10 is extremely strict and requires precise type hints everywhere. You can start with a lower level and work your way up.

    The paths parameter tells PHPStan which directories to analyse.

  3. Run PHPStan

    Execute PHPStan from the command line:
     
    libraries/vendor/bin/phpstan analyse
    

    PHPStan will analyse your code and report any issues it finds. Each issue includes a file path, line number, and description of the problem.

Deprecated code

To check for the use of deprecated code you have to install the phpstan package with deprecation rules. Add it to your composer.json: 

 "require-dev": {
    "phpstan/phpstan": "^2.1.29",
    "phpstan/phpstan-deprecation-rules": "^2.0.3"
  },

And add it to your phpstan.neon configuration file:

includes:
    - libraries/vendor/phpstan/phpstan-deprecation-rules/rules.neon

PHPStan reads the “deprecated” annotations in the docblocks. 


So, if you still have some Factory::getDbo() in your code, you’ll get a neat message like:               

 Call to deprecated method getDbo() of class Joomla\CMS\Factory:
    4.3 will be removed in 7.0
    Use the database service in the DI container
    Example: Factory::getContainer()->get(DatabaseInterface::class);

Telling you what is deprecated and what to use instead. You’ll also get information about in what file and on what line the error is found.

Levels

PHPStan has levels, ranging from 0 to 10, higher levels using more strict typing. When you’d  only use level 0 for the most obvious errors like undefined variables, supplemented with deprecation validations, you’ll already get a lot of information. Better ensure your code is at least level 5.

Here is a brief overview of what is  examined on each level. Levels are cumulative, so running level 5 also gives you all the checks from levels 0-4.

  1.  unknown classes, functions and methods called on $this, wrong number of arguments passed to those methods and functions, always undefined variables.
  2.  possibly undefined variables, unknown magic methods and properties.
  3.  unknown methods, validating PHPDocs return types, types assigned to properties.
  4.  return types, types assigned to properties.
  5.  dead code reporting.
  6.  checking types of arguments passed to methods and functions.
  7.  missing typehints.
  8.  partially wrong union types.
  9.  calling methods and accessing properties on nullable types.
  10.  strict use of explicit mixed type.
  11. (new in PHPStan 2.0) even more strict use of mixed type.

You see, you get a nice toolbox already out of the box. If you need more, there are also a lot of extensions, like we saw for deprecations. Unfortunately there is not yet a package with rules specific for Joomla. PHPStan extensions are easily installable via Composer; you can find them on Packagist.

Custom rules

You can also apply custom rules. For instance if you want to examine if all dependencies are nicely injected or to check if the MVCFactory is used to instantiate MVC classes. For that you can write your own rules.

Coding custom rules is not very difficult. You need to make a class that implements two methods:

  • getNodeType(): for what kind of PHP code (what “node type”) do you want to have a rule?
  • processNode(): what exactly do you want to verify? This method returns an array of errors.

You can find all about creating custom rules in the documentation.

Reading tip: the book “Recipes for Decoupling” by Matthias Noback (Leanpub 2022). In chapter one he shows how to make custom rules for PHPStan. In the rest of the book he gives advice on how to accomplish loose coupling in  your code and then also writes custom rules to verify if your code follows the advised best practices. 

Hiding known issues with the baseline-file

When you are working with a legacy Joomla extension, already existing for quite some years, there is a good chance that you’ll get a whole list of  violations when  running PHPStan for the first time. In practice it is often not possible to resolve all deprecations at once. The problem is, when you have new features or bug fixes, that you might miss out on real important errors when they are hidden in a report with a huge list of deprecations.

This is where the baseline-file is meant for. In this file you can list all  issues that you know of and that you have to work on. The errors in this “baseline” list will be hidden in subsequent runs. In that way you can focus on rule violations in new and changed code.

Such a baseline file can be generated from the command line by running PHPStan with the --generate-baseline option. The list will be stored in the phpstan-baseline.neon file. If you want to use it, you must include it in the PHPStan configuration file (phpstan.neon):

includes:
 - phpstan-baseline.neon

Joomla uses this too. That’s why you only see a report for rule violations of new code when you make a pull request. However, be careful: you no longer see the bugs when they are hidden by the baseline file, but they still have to be fixed someday…

Integration with your GitHub workflow

In GitHub you can run a workflow: a series of tasks (“actions”), triggered by an event. You have to make a yaml file in .github/workflows. You can see examples of that in the .github/workflows/ci.yml file in the Joomla CMS repo and the .github/workflows/cy.yml file of the Weblinks repo. Those workflows are run on every push, and pull request.

You can also use a pre-built GitHub Action for PHPStan, but note that Joomla by default installs the vendor directories under /libraries.

There are many tutorials for GitHub Actions, here are a few. This is a subject for another article.

You can also integrate PHPStan in the build process of your extension.

Conclusion

PHPStan is an indispensable, free, automated tool to check the code of your extension. You can start at a low level to catch the worst bugs. After fixing them you can gradually raise the level. Fixing all issues will improve the quality of your code. You can tailor rules specifically to your extension. Integrating it in your CI workflow will guard that quality.

Next month: testing accessibility

The next episode in this series will be about how to test the accessibility of your Joomla extension. We will also use some tests in Cypress and custom rules in PHPStan for that.


Links

The articles in this series about testing your Joomla extension, until now:

  1. PHPUnit: test an isolated piece of software (a “unit”) in a controlled environment, without the extension being installed in a Joomla site.
  2. Cypress: end-to-end tests, running the extension in a Joomla site, verifying if it does what we expect it to do.
  3. PHPStan (this article): static code analysis to check source code on errors.

Some articles published on the Joomla Community Magazine represent the personal opinion or experience of the Author on the specific topic and might not be aligned to the official position of the Joomla Project

0
The January Issue
 

Comments

Already Registered? Login Here
No comments made yet. Be the first to submit a comment

By accepting you will be accessing a service provided by a third-party external to https://magazine.joomla.org/