Skip to content

Match custom extension files to analysed paths by realpath so symlinked packages are not reported as outside analysed paths#6015

Open
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-e4l4mo1
Open

Match custom extension files to analysed paths by realpath so symlinked packages are not reported as outside analysed paths#6015
phpstan-bot wants to merge 1 commit into
phpstan:2.2.xfrom
phpstan-bot:create-pull-request/patch-e4l4mo1

Conversation

@phpstan-bot

Copy link
Copy Markdown
Collaborator

Summary

When a custom PHPStan extension (rule, service, etc.) is provided by a package that is symlinked into vendor (for example a Composer path repository, a common mono-repo setup) and that package's source is included in the analysed paths, PHPStan still printed:

Result cache might not behave correctly.
You're using custom extension in your project config
but this extension is not part of analysed paths:

- PhpstanExt\Ext\MyRule

even though the extension is analysed. This is a false positive.

Changes

  • src/Analyser/ResultCache/ResultCacheManager.php
    • getProjectExtensionFiles() now, when a service class file is not found directly in the analysed dependency graph and is not located under a vendor directory, tries to match it to an analysed file by realpath before treating it as outside of analysed paths.
    • New helper findAnalysedFileNameByRealpath() builds a lazy map from realpath(dependencyKey) => dependencyKey and resolves the reflection file name against it. The map is only built when needed, so non-symlinked projects are unaffected in behaviour and cost.
    • Added a ?array $analysedFileNamesByRealpath cache property, reset at the start of getProjectExtensionFiles().
  • .github/workflows/e2e-tests.yml + e2e/result-cache-symlink/
    • New end-to-end test: an ext package is symlinked into the app's vendor through a Composer path repository and its src is added to the analysed paths. After modifying the rule, the run must not contain Result cache might not behave correctly.

Root cause

ReflectionClass::getFileName() returns the realpath of the class file (PHP resolves symlinks when including a file), whereas the analysed file/dependency keys are the paths as traversed from the configured paths — i.e. the symlinked path (vendor/.../src/MyRule.php). The two never matched, so getAllDependencies() returned nothing, the file wasn't under the app's vendor realpath (it resolves to somewhere outside, e.g. ../ext/src), and it was recorded as isAnalysed = false, triggering the warning.

The fix reconciles the two path forms by comparing realpaths: realpath(symlinkedAnalysedPath) === realpath(reflectionRealpath), so a symlinked-in extension that is actually analysed is correctly recognized. Genuinely non-analysed extensions (e.g. those living only in vendor or an unanalysed build/ dir) still fail the realpath lookup and are reported as before.

Test

  • e2e/result-cache-symlink reproduces the reported setup with a Composer path-repository symlink and asserts the false-positive warning is gone. Verified it fails without the fix (the warning is printed) and passes with it.
  • Probed the parallel path in the same method (extensions living in a real vendor dir, and extensions in an unanalysed dir such as e2e/result-cache-8's build/): both still correctly report the extension as outside analysed paths, because their realpath is not among the analysed files — no regression. e2e/result-cache-8 continues to assert the warning is shown.

Fixes phpstan/phpstan#14924

…ed packages are not reported as outside analysed paths

- ResultCacheManager::getProjectExtensionFiles() looks up the class file reported by reflection in the analysed dependency graph. Reflection returns the file's realpath, while analysed paths may reach the same file through a symlink (e.g. a Composer path repository symlinked into vendor), so the direct lookup missed it and the file was wrongly treated as not analysed.
- Add findAnalysedFileNameByRealpath() which resolves the reflection file name to the analysed dependency key by comparing realpaths, and use it as a fallback before concluding an extension is outside of analysed paths.
- The realpath map over the dependency graph is built lazily and only when a service class is neither found directly nor located under a vendor dir, so normal (non-symlinked) projects pay no extra cost.
- Add e2e/result-cache-symlink covering a custom rule provided by a package symlinked into vendor via a Composer path repository; modifying the rule must not print "Result cache might not behave correctly".
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.

PHPStan incorrectly reports "extension is not part of analysed paths"

2 participants