PHPStan performance in wordpress-develop: avoid pointing scanDirectories at directories containing vendor/ or node_modules/
composer phpstan slow; PhpStorm's on-the-fly PHPStan inspection lags badly (many seconds per save).
PHPStan walks every .php file under each entry in scanDirectories to build its symbol table. If an entry contains vendor/ or node_modules/, every third-party file gets parsed on every run.
excludePaths: analyseAndScan does not meaningfully help here: excludes are applied after enumeration, so the traversal and parsing cost is mostly still paid. In testing, enabling excludes only saved ~2s out of a 34s warm run.
Point scanDirectories at the source subdirectories of the plugins/tools you need symbols from — never at a directory that contains vendor/ or node_modules/. Use scanFiles for individual entry-point files at the package root.
Example from phpstan.neon:
# Bad — pulls in ~9000 third-party files
scanDirectories:
- src/wp-content/plugins/performance
- src/wp-content/plugins/ai
# Good — only source dirs
scanDirectories:
- src/wp-content/plugins/performance/plugins
- src/wp-content/plugins/ai/includes
- src/wp-content/plugins/ai/src
- src/wp-content/plugins/ai/routes
scanFiles:
- src/wp-content/plugins/performance/performance.php
- src/wp-content/plugins/ai/ai.php| Scenario | Before | After |
|---|---|---|
| Cold full run | 86s | 33s |
| Warm full run | 34s | 8s |
Single-file (PhpStorm --tmp-file/--instead-of) |
8–10s | ~1s |
-
Warm the cache:
composer phpstanonce. -
Time a single-file editor-mode run, which is what PhpStorm does per inspection pass:
FILE=src/wp-includes/functions.php time ./vendor/bin/phpstan analyse --memory-limit=2G \ --error-format=json --tmp-file=$FILE --instead-of=$FILE > /dev/null
If this is >2s, something in config is over-scanning.
-
Compare against
--configuration=phpstan.neon.dist(no local scanDirectories). If that one is fast and your config is slow, the difference is in yourscanDirectories/paths. -
For any directory you're scanning, check for
vendor/ornode_modules/subtrees:find <scanDir>/vendor <scanDir>/node_modules -name '*.php' 2>/dev/null | wc -l
level(0 vs 10): negligible difference on single-file timing.--memory-limit: not the bottleneck at 2G.parallel.*tuning: irrelevant for single-file editor mode (runs in one process anyway).- Result cache size (tens of MB is fine): loading it is cheap compared to scanning thousands of extra files.