Skip to content

chore(phpstan): make the shared base PHPStan-2 ready (unblocks 9 fleet dependabot PRs) - #517

Merged
rubenvdlinde merged 1 commit into
mainfrom
chore/phpstan-2-migration-base
Aug 20, 2026
Merged

chore(phpstan): make the shared base PHPStan-2 ready (unblocks 9 fleet dependabot PRs)#517
rubenvdlinde merged 1 commit into
mainfrom
chore/phpstan-2-migration-base

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Why

Nine dependabot PRs bumping phpstan/phpstan 1.12.33 → 2.x are red across the fleet — decidesk#526, doriath#259, hermiq#363, larpingapp#342, openbuild#236, pipelinq#1171, portaliq#158, scholiq#430, shillinq#889. None of them fail on a real defect. They fail on two migration-level behaviour changes, so the fix belongs in the shared base once rather than in eighteen app configs.

Measured on decidesk with phpstan 2.2.8 — 60 errors:

count identifier cause
17 phpDoc.parseError @SuppressWarnings((PHPMD.UnusedFormalParameter) …)
18 function.alreadyNarrowedType type known only from a docblock
13 identical.alwaysTrue same
4 throws.unusedType genuine
8 other genuine

What this changes

treatPhpDocTypesAsCertain: false — 2.x narrows types declared only in a docblock, then reports every defensive check against them as already-decided. The flagged guards cross an app boundary (OpenRegister contracts) where the docblock is the only contract there is. Deleting them to satisfy a stricter reading of an unenforced type would remove real runtime protection, so the setting restores 1.x behaviour instead. No effect on 1.x, which does not narrow from PHPDoc at all.

ignoreErrors: phpDoc.parseError for @SuppressWarnings — that tag is PHPMD's, and the phpmd gate reads it. 2.x began parsing it as its own tag and fails on PHPMD's rule syntax (Unexpected token ".UnusedFormalParameter)", expected ")"). This is PHPStan commenting on a neighbour's syntax, not a defect.

Result on decidesk: 60 → 22 errors. The residual 22 are genuine per-app debt (throws.unusedType, parameterByRef.type, argument.type) and are deliberately not masked.

Verification on both majors

17 of 18 apps still run 1.12.33, so a 2.x-only config key here would break the fleet. Tested against decidesk:

result
phpstan 2.2.8 60 → 22 errors
phpstan 1.12.33 exit 0, [OK] No errors, no configuration error

And a positive control on 1.12.33 — a deliberately broken file still produced Found 1 error, confirming it is analysing rather than silently skipping. That "analysed ZERO files" failure mode is the one this file's own header documents from v1.7.0/v1.7.1, so it was checked explicitly rather than inferred from a green exit code.

Not merged

Gates ship from .github@main to all 18 apps the moment this lands, so I have left it for a human. It is not covered by the app-level development merge authorisation.

Nine dependabot PRs bumping phpstan/phpstan 1.12.33 -> 2.x are red across the
fleet, all on the same two migration-level classes rather than on real defects.
Measured on decidesk with phpstan 2.2.8: 60 errors, of which

  17  phpDoc.parseError          @SuppressWarnings((PHPMD.UnusedFormalParameter) …)
  21  alreadyNarrowedType /      defensive checks against a type known only
      identical.alwaysTrue       from a docblock

Both are addressed in the shared base so the fix lands once, not eighteen times:

  treatPhpDocTypesAsCertain: false   restores 1.x behaviour. The guards it flags
                                     cross an app boundary (OpenRegister
                                     contracts) where the docblock is the only
                                     contract; deleting them to satisfy a
                                     stricter reading of an unenforced type
                                     would remove real runtime protection.

  ignoreErrors phpDoc.parseError     @SuppressWarnings is PHPMD's annotation and
                                     is read by the phpmd gate. 2.x parses it as
                                     its own tag and fails on PHPMD's syntax.

decidesk: 60 -> 22 errors. The residual 22 are genuine per-app debt
(throws.unusedType, parameterByRef.type, argument.type) and are NOT masked here.

Verified on BOTH majors, since 17 of 18 apps still run 1.12.33:
  phpstan 2.2.8   60 -> 22 errors
  phpstan 1.12.33 exit 0, '[OK] No errors', no configuration error
                  and still 'Found 1 error' on a deliberately broken file —
                  so it is analysing, not silently skipping (the failure mode
                  this file's own header documents).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants