More dependency tracking: directories, classes, declarations, closure and type-specifying extensions - #6659
Merged
Conversation
…cies Their scope is Scope&DependencyTracker, the same as for dynamic return type extensions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
Their scope is Scope&DependencyTracker, the same as for dynamic return type extensions. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
Contributor
|
oh wow. I did not imagine you would go this far with this new extension type. its really cool <3. |
Member
Author
|
I'm going even further 😂 #6662 |
The analysis of the current file depends on which files there are in a directory, recursively, whose names match a pattern - a directory scanned for configuration files, templates or migrations. The file is analysed again when a matching file is created, deleted or renamed, or the directory itself is. What the files contain is not part of it, that's trackFileDependency(). It's a value dependency through DirectoryResultCacheValueExtension, the directory is stored relative like the paths of trackFileDependency(). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
The analysis of the current file depends on a class as if the code referenced it - a class named in a string, in a PHPDoc tag PHPStan does not resolve, or in a configuration file. The file is analysed again when the class or one of its parents, interfaces or traits changes what it declares (signatures and PHPDocs, not method bodies - the same exported nodes that decide it for a class referenced in code), and when the class is created, deleted or moved to another file. It's a value dependency through ClassResultCacheValueExtension. The class name keeps its case: looking up a class that is not loaded yet goes through the autoloader's PSR-4 rules, which follow it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
Class reflection extensions and other extensions describing a class get no Scope, and what they declare is remembered and reused by every file analysed after it without asking the extension again. So it's the class that depends on what the extension read, not the file being analysed. DeclarationDependencyTracker, injected into such an extension, records the value, file, directory or class dependency for the class. The values are kept for the whole process, and every file depending on the class - or on a class extending or implementing it - gets them as its own dependencies when its analysis finishes. When one changes, all those files are analysed again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ
Contributor
|
Can we also get for property extensions? In Larastan I read the database migrations when a undefined property is accessed. |
ondrejmirtes
force-pushed
the
dependency-tracker-scenarios
branch
from
October 2, 2026 15:29
6f58af6 to
2c7c9cf
Compare
Member
Author
|
@canvural Yeah it's in there, just inject DeclarationDependencyTracker. I actually have a report locally for the 50 most popular extensions and their gaps where result cache gets stale which could be filled and fixed with these new methods. I plan to go through it after 2.3.0 release. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
More scenarios for tracking result cache dependencies, from a survey of where popular extensions make the result cache stale.
Scope&DependencyTracker(Pest'sextend()types$thisin test closures fromtests/Pest.php).Scope&DependencyTracker(webmozart/assert'simplementsInterface()with a class named in a string).DependencyTracker::trackDirectoryDependency($directory, $pattern = '*'): the analysis depends on which files there are in a directory, recursively, whose names match a pattern - Larastan's views, migrations, Doctrine mapping directories, Drupal module discovery, finding everyPest.php. Only which files exist, not their contents (that'strackFileDependency()).DependencyTracker::trackClassDependency($className): the analysis depends on a class as if the code referenced it - a class named in a string (DQL,config('Name'),@covers,@dataProvider Other::method,class_exists()on a built name). The value is the exported nodes of the class and its ancestors, so a changed signature or PHPDoc counts and an edited method body doesn't, and the class being created, deleted or moved is noticed.DeclarationDependencyTracker: for extensions that get noScopebecause they describe a class - class reflection extensions adding magic methods and properties (Carbon macros, Larastan model properties, Faker providers, Magento extension attributes, Doctrine'sfindByX()). Inject it and track on theClassReflection. What such an extension declares is reused by every file analysed after it without asking the extension again, so the values are kept per class for the whole process, and every file depending on the class - or a class extending or implementing it - gets them as its own dependencies.One commit each, plus the turbo version bump for the two new
MutatingScopemethods.e2e/result-cache-value-dependencyhas a step for each: a closure$thisextension, a type-specifying extension, a rule looking for a template in a directory, a dynamic return type extension naming a class in a string (created outside the analysed paths, then its parent's method body and signature changed), and a magic method of a class reflection extension called from two files. Each step fails when its tracking call is removed.🤖 Generated with Claude Code
https://claude.ai/code/session_01GdwtgiEHwkF5BTz73jgHQQ