Skip to content

Add external file dependency tracking for incremental cache invalidation#5364

Draft
janedbal wants to merge 2 commits intophpstan:2.1.xfrom
janedbal:external-file-dependencies
Draft

Add external file dependency tracking for incremental cache invalidation#5364
janedbal wants to merge 2 commits intophpstan:2.1.xfrom
janedbal:external-file-dependencies

Conversation

@janedbal
Copy link
Copy Markdown
Contributor

@janedbal janedbal commented Mar 31, 2026

Summary

  • Introduces ExternalFileDependencyRegistrar, a new @api service that allows extensions to declare that an analyzed file depends on an external (non-analyzed) file
  • When that external file changes, only the dependent analyzed files are re-analyzed instead of triggering full cache invalidation
  • This is an alternative to ResultCacheMetaExtension for cases where external data changes should cause surgical re-analysis of affected files only, not full invalidation

Motivation

ResultCacheMetaExtension is an all-or-nothing mechanism — when any extension's hash changes, the entire result cache is invalidated. phpstan-symfony uses this to track Symfony DI container changes, but any service definition change (even trivially adding a dependency via autowiring) causes full re-analysis of the entire project. On large codebases this is devastating.

This PR adds a granular alternative: extensions register per-file dependencies on external files during analysis. When those files change, only the affected analyzed files (and their dependents) are re-analyzed.

See companion PR: phpstan/phpstan-symfony#478

Changes

  • New ExternalFileDependencyRegistrar service (@api) with add(string $externalFilePath) method
  • FileAnalyserResult / AnalyserResult carry external dependencies through the pipeline
  • WorkerCommand / ParallelAnalyser serialize and aggregate external deps across parallel workers
  • ResultCacheManager:
    • restore(): checks external file hashes, adds dependent files to incremental re-analysis list
    • save(): inverts and stores external dependencies with file hashes
    • process(): merges cached + fresh external dependencies

Test plan

  • All 11,719 existing tests pass
  • Add dedicated tests for external dependency cache invalidation

Co-Authored-By: Claude Code

Introduces ExternalFileDependencyRegistrar, a new @api service that allows
extensions to declare that an analyzed file depends on an external (non-analyzed)
file. When that external file changes, only the dependent analyzed files are
re-analyzed instead of triggering full cache invalidation.

This is an alternative to ResultCacheMetaExtension for cases where external
data changes (e.g. Symfony DI container XML) should not cause full cache
invalidation but rather surgical re-analysis of affected files only.

The mechanism integrates with the existing result cache system: external
dependencies are tracked during analysis, stored in the cache alongside
regular file dependencies, and checked during cache restore.

Co-Authored-By: Claude Code
@ondrejmirtes
Copy link
Copy Markdown
Member

Smells a lot of like vibecoding, I don't like the API. I think a special Collector, especially now when we have CollectedDataEmitter on 2.2.x, might work better.

- Use early exit (continue) instead of nested if blocks in ResultCacheManager
- Add missing `use function count` in WorkerCommand
- Remove same-namespace import in AnalyserTest
- Fix mergeExternalFileDependencies: strip hashes from cached data in
  restore() before passing to ResultCache, matching the pattern used
  by regular dependencies

Co-Authored-By: Claude Code
@janedbal
Copy link
Copy Markdown
Contributor Author

I think a special Collector might work better

Do you mean the phpstan-symfony wont call any addExternalDependency(xml) upon Container::get() calls, but we rather create separate special collector that finds those usages and add those to the dependency graph somehow?

@ondrejmirtes
Copy link
Copy Markdown
Member

I don't know. I posted phpstan/phpstan-symfony#455 (comment) after some thinking, this needs deeper analysis and a different idea.

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