levelmax World 3 PHPStan to Max
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. B
L
Level 3.1 ~15 min Target: PHPStan level 9

Living with a baseline

Raise a real project from level 6 to level 9 and PHPStan finds four hundred errors. You can't fix them in one pull request, and you can't merge a red build. A baseline gets you out of that corner:

vendor/bin/phpstan analyse --generate-baseline

PHPStan writes every current error to phpstan-baseline.neon, and your phpstan.neon includes it:

includes:
    - phpstan-baseline.neon

The build goes green at the new level straight away. New code is held to level 9 from now on, and the old errors are written down instead of forgotten. That's the deal a baseline offers: no new errors today, and fewer old ones every week.

What an entry is

-
    message: '#^Cannot access property \$pence on Zone\|null\.$#'
    identifier: property.nonObject
    count: 1
    path: src/ShippingQuote.php

Each entry ignores one message, in one file, a set number of times. It has no line number, so the entry survives edits elsewhere in the file. A second error with the same message in the same file goes over the count, and PHPStan reports it.

Read that entry again. It isn't a style complaint. It says that ShippingQuote reads a property from something that can be null, and every country without a zone of its own will crash it. A baseline entry is a bug report with the line number taken off. Some are cosmetic. Some are the reason a customer can't check out.

Make it shrink

PHPStan reports an entry that no longer matches anything:

Ignored error pattern #^Cannot access property \$pence on Zone\|null\.$#
(property.nonObject) in path src/ShippingQuote.php was not matched in reported errors.

That isn't PHPStan nagging. It means you fixed something, so delete the entry and the baseline is one shorter. reportUnmatchedIgnoredErrors is on by default. Leave it on, or the baseline only ever grows.

A good habit: when you open a file for any reason, fix its baseline entries too and leave it with none. Every pull request moves the number down a little.

Ignore one thing, and say why

Sometimes PHPStan is wrong, or a library's types are. You need to ignore one error on one line, not a whole file of them, and not in the baseline, which is for debt you mean to pay off. Use an inline ignore with the identifier:

// @phpstan-ignore argument.type (the SDK's stub says string, but it accepts an Address; VENDOR-311)
$this->courier->send($parcel->address);

Only argument.type on that line is ignored. Any other error on it is still reported, and if the SDK fixes its stub, PHPStan reports the ignore as unmatched. The bare @phpstan-ignore-line and @phpstan-ignore-next-line hide everything on the line, including whatever goes wrong there next year.

phpstan-baseline.neon, January12 entries
phpstan-baseline.neon, June340 entries
Nobody added an error. Everybody regenerated the baseline.

Level 9

This exercise runs at level 9, which is strict about explicit mixed. Once something is declared mixed, the only thing you can do with it is pass it on to something else that accepts mixed. You can't add it, concatenate it, or call methods on it. array<string, mixed> is the usual way mixed gets in: it fixes a level 6 error by saying nothing at all about the values.

Power-up

Before
<?php

final class DispatchParcel
{
    public function __invoke(Parcel $parcel): void
    {
        // @phpstan-ignore-next-line
        $this->courier->send($parcel->address, $parcel->weightInGrams());
    }
}
After
<?php

final class DispatchParcel
{
    public function __invoke(Parcel $parcel): void
    {
        // @phpstan-ignore argument.type (the SDK's stub says string, but it accepts an Address; VENDOR-311)
        $this->courier->send($parcel->address, $parcel->weightInGrams());
    }
}

Quick check

1. A pull request turns the build red with one new level 9 error. Someone runs --generate-baseline and the build goes green. What happened?
2. You fix a bug, and PHPStan now reports "Ignored error pattern … was not matched in reported errors." What should you do?
3. Which inline ignore still reports a new, unrelated error on the same line?

CHALLENGE

The team raised this project to level 9 with a baseline, and it has stayed green ever since. Meanwhile, customers in countries without a shipping zone of their own get an error page at checkout. The bug has been in phpstan-baseline.neon the whole time.

Fix ShippingQuote and clear every one of its baseline entries, so the file is clean at level 9 with no help. LabelPrinter is legacy code for another day, so its entries stay.

Running on PHP 8.3…

Run the tests, run PHPStan, or submit for both. Ctrl/⌘ + Enter submits from the editor.