Files
pl_mail/phpstan.dist.neon
T
Paul LütznerandClaude Opus 5 1d442f9452 build: PHPStan could not run from a working checkout
`paths: ./` walks the tree with a RecursiveDirectoryIterator *before*
excludePaths are applied, so a directory it cannot read is a hard crash rather
than a skip — and no exclusion can prevent it, because the crash happens during
enumeration. `.claude/worktrees` holds entire git worktrees of this repository,
37 of them here, several carrying a root-owned `var/secrets` left behind by a
container. `composer stan` therefore died on a directory belonging to a
different checkout of itself, and adding the path to excludePaths did nothing
at all.

The top-level directories that hold PHP are named instead. CI on a fresh clone
was always fine, which is exactly why this could sit here: it only breaks for
somebody running the gate on the machine they work on.

The existing excludePaths are left as they are. Most are no longer reachable
from `paths` and are belt and braces now, but they still document what is not
project code, and they come back into force the moment anyone widens `paths`
again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 13:53:46 +02:00

109 lines
4.9 KiB
Plaintext

# Static analysis.
#
# Level 5 with everything already failing captured in a baseline. The
# alternative — level 0 with no baseline — reports nothing interesting, and
# level 9 buys a fortnight of triage before it finds its first real bug. Five is
# where the class of defect this codebase actually has starts showing up:
# nullable entity getters (`?int getId()`, `?Account getAccount()`) flowing into
# parameters and return types that are not nullable.
#
# The baseline is a debt ledger, not a licence. It shrinks; raise the level when
# it is small enough that doing so is a day's work rather than a fortnight's.
includes:
- phpstan-baseline.neon
- vendor/phpstan/phpstan-symfony/extension.neon
- vendor/phpstan/phpstan-symfony/rules.neon
- vendor/phpstan/phpstan-doctrine/extension.neon
- vendor/phpstan/phpstan-doctrine/rules.neon
parameters:
level: 5
# The whole project rather than just src/ and tests/: bin/, config/ and
# public/ hold real PHP that can break the same way anything else does —
# config/bootstrap_generated_secrets.php is loaded into every process
# through composer's autoload.files.
#
# Listed explicitly rather than as `./`, and that is not a style
# preference. PHPStan walks the tree with a RecursiveDirectoryIterator
# *before* it applies excludePaths, so a directory it cannot read is a hard
# crash rather than a skip — and no exclusion can prevent it, because the
# crash happens during enumeration. `.claude/worktrees` holds entire git
# worktrees of this repository (37 of them at the time of writing), several
# containing a root-owned `var/secrets` left by a container. With `./` the
# gate could not be run at all from a working checkout: it died on a
# directory belonging to a different checkout of itself.
#
# Everything with PHP in it is named below. A new top-level directory that
# holds code has to be added here, which is the cost of this; the previous
# arrangement's cost was that the command did not run.
paths:
- bin
- config
- public
- src
- tests
- importmap.php
excludePaths:
analyseAndScan:
# Not PHP.
- assets
- docs
- frankenphp
- templates
- translations
# Third-party code, and its symbols come from the autoloader
# anyway — analysing it reports other people's debt.
- vendor
- node_modules (?)
# Build and run artefacts. public/assets is compiled output and
# var/ holds the container dumps this very config reads.
#
# The `(?)` marks a path that may legitimately be absent: PHPStan
# treats an excludePath that matches nothing as a config error, and
# none of these exist on a fresh clone or in a CI job that has not
# built assets yet.
- public/assets (?)
- public/bundles (?)
- var (?)
- playwright (?)
- playwright-report (?)
- test-results (?)
# Generated: 54 migrations nobody edits by hand, and a ~1000-line
# dump of the framework config tree that exists to be read.
- migrations
- config/reference.php
# Twig extensions lifted from upstream rather than written here.
# NOTE: only Vendor/ — the rest of src/Twig is application code
# (SidebarCounts and friends) and must stay analysed.
- src/Twig/Vendor
# ignoreErrors is deliberately empty.
#
# The rule usually wanted here — silencing "Property …::$id (int|null) is
# never assigned int" on Doctrine identifiers — is not needed: the
# phpstan-doctrine extension included above already understands that the
# ORM assigns identifiers by reflection, so the error never fires. Adding
# the pattern anyway would fail the build, because PHPStan treats an ignore
# that matches nothing as a config error.
#
# What property.onlyWritten does catch in this project is a different and
# genuine thing: controllers injecting repositories they never use
# (MailController::$mailboxRepository, ComposeController::$mailboxRepository
# and about fifteen more). Those are in the baseline as debt to delete, not
# noise to suppress — which is why there is no blanket ignore for them.
ignoreErrors: []
# Docblocks here are prose as much as types — plenty predate the signatures
# beside them. Trusting them as certain turns a stale @var into a stream of
# "condition is always false" on code that is fine.
treatPhpDocTypesAsCertain: false
symfony:
containerXmlPath: var/cache/test/App_KernelTestDebugContainer.xml
consoleApplicationLoader: tests/phpstan-console-loader.php
doctrine:
objectManagerLoader: tests/phpstan-object-manager.php