php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Fixing Bugs With PHPStan

Posted by on August 16, 2023

I don’t need to tell you, but as developers, bugs are a constant challenge. We can do everything in our power to test the code that we’re writing as we’re writing it. But that’s just not good enough. That’s because our changes affect other people’s code in ways we can’t expect, and their changes affect our previously tested code. These changes can create a bug. If our customers find the bug, it makes them mad, and mad customers stop paying for our services. Wouldn’t it be great to find a tool that can find bugs before our customers?

In this article, we’ll discuss how to use PHPStan to find bugs before our customers find them in production. We’ll talk about how to install it, configure it, do an initial setup, and run it on the command line.

What is PHPStan?

PHPStan is a static code analysis tool that we can use to validate our program for correctness. Because it’s a static code analysis tool, we don’t actually need to run our code, and instead, it inspects every line of our code to validate it’s correct.

As part of this process, it identifies bugs and inconsistencies in our code early so a customer doesn’t find them at all. It also improves code quality and maintainability by using some of its more advanced rules. We can use PHPStan to enhance developer productivity because it’s helping to look out for bugs we may not see until we do dynamic testing.

PHPStan can find errors like calling a non-existent method, calling a method with the wrong parameter types, and calling a method on a null value. It’ll also find errors like the wrong return type or forgetting to return the result in a method with a defined return type.

I’m a huge fan of static code analysis for a wide variety of things, including bug reduction, so PHPStan is a perfect tool to add to our programming toolbox.

Why Use PHPStan?

Now let’s discuss an example of what PHPStan can prevent.

In one file we might have this User class where we have a public integer that’s the ID.

class User
{
    public int $id = 1;
}

In another file, we’re going to create a new User class and then display that ID.

$user = new User();
echo $user->id;

At the point the code is written everything works and we can go about our day, but eventually we’re going to realize that we need to refactor to properly encapsulate the ID. In this process we’ll change that public property to a private property.

class User
{
    private int $id = 1;
}

We’re going to test the code that we’re working on and see that everything is working appropriately and then move on with our day. But we’re not going to see that other file that’s accessing the ID property. Without something in place to catch this our users are going to be the ones finding this error.

It’s hard to find these kinds of problems because they can be lurking anywhere in our code base. But PHPStan can find it with ease.

Installing

Installing PHPStan is simple, we just install it using Composer.

php composer.phar require --dev phpstan/phpstan

Make sure it’s a dev requirement so it doesn’t get installed on the production server.

Running

To run PHPStan we’ll use the command below.

./vendor/bin/phpstan analyze -l3 app tests

This causes PHPStan to run its “analyze” command at level 3 for the PHP files in the “app” and “tests” directories.

Depending on your code base, it might spit out a few errors or it might spit out a few thousand.

My example project has hundreds of them. There are two ways that we can get this down more to a manageable level. The first is that we can adjust the level.

Levels

The level parameter is going to help control both the number of errors that PHPStan finds and how much time it spends running. This is because the higher the number, the more rules that PHPStan will run against our code.

The list below shows what PHPStan will check at each level. It’s important to know that the levels stack so running at level 3 also includes levels 0, 1, and 2. We’ve take this list directly from the PHPstan documentation

  1. basic checks, unknown classes, unknown functions, unknown 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 on classes with call and get
  3. unknown methods checked on all expressions (not just $this), validating PHPDocs
  4. return types, types assigned to properties
  5. basic dead code checking – always false instanceof and other type checks, dead else branches, unreachable code after return; etc.
  6. checking types of arguments passed to methods and functions
  7. report missing typehints
  8. report partially wrong union types – if you call a method that only exists on some types in a union type, level 7 starts to report that; other possibly incorrect situations
  9. report calling methods and accessing properties on nullable types
  10. be strict about the mixed type – the only allowed operation you can do with it is to pass it to another mixed

I’m going to start at level 3 as it gives us a good balance without an unmanageable level of errors in this specific code base. I want to eventually get it to at least at level 5 because I want that argument checking.

Fixing Our Errors

Now, we still have a lot of errors that we have to clean up and there are two options for what we can do here.

The first is that we can fix all the errors. That’s doable but it might take a lot of time that we don’t want to spend right now.

The second option is that we can ignore the initial set of errors and only fix new errors.

A baseline file helps us define the expected errors and warnings for our existing code base. It’s a great way to tackle legacy projects with a large number of issues without being overwhelmed.

To generate our baseline, we will run PHPStan with the --generate-baseline command line argument.

./vendor/bin/phpstan analyze -l3 --generate-baseline app tests
Note: Using configuration file /Users/scottkeck-warren/Projects/test/phpstan.neon.
 182/182 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%



 [OK] Baseline generated with 391 errors.

If we look inside this file, it’s essentially a long list of all of the errors that we saw before, but in a format that PHPStan can read.

Now if we run PHPStan again with the baseline file included, it won’t find any errors.

./vendor/bin/phpstan analyze -l3 -c phpstan-baseline.neon app tests

Only new issues will show up as they’re found in our codebase so we only need to focus on those.

Configuration Files

Now the command line tool is getting a little out of hand and I’m getting sick of typing it over and over again. PHPStan has our backs by allowing us to use configuration files.

In the configuration file, we can specify various settings like the level of strictness, directories to include or exclude from analysis, and much more. It’s a powerful tool for tailoring PHPStan to our specific project requirements.

PHPStan uses a configuration format called NEON. It’s very similar to YAML, so if you’re familiar with YAML, you can also write NEON.

We’re going to convert our command line into this configuration file.

includes:
    - phpstan-baseline.neon

parameters:
    level: 3
    paths:
        - app
        - tests

By default, PHP looks for the “phpstan.neon” file by default, so it’s best to name the file “phpstan.neon” so we can reduce what we’re typing as much as possible.

Now we can run it with no parameters at all.

./vendor/bin/phpstan analyze

When to Run PHPStan

Now you might be wondering how we can run PHPStan to catch these bugs and I like to run PHPStan in two places.

The first place I like to add it is as part of my GitHub Actions. This makes sure that as I push code to GitHub it’s checking all of the files and if it finds an error it’ll report it back to me and not allow bugs to reach my customers.

I also like to run it as part of my pre-commit script inside of the git hooks directory. This gives me faster feedback on my changes than GitHub can provide.

This two-tiered approach gives us a safe base by running the tool on all the files in the GitHub actions and also quickly getting feedback on just the files we’re changing locally.

What You Need to Know

  1. PHPStan is a static code analyzer that finds bugs
  2. Pick a level and run the tool to find any errors
  3. Generate a baseline to ignore existing errors
  4. Run it locally and as part of the CI/CD process

Tags:
 

Leave a comment

Use the form below to leave a comment: