Skip to content

More dependency tracking: directories, classes, declarations, closure and type-specifying extensions - #6659

Merged
ondrejmirtes merged 6 commits into
2.3.xfrom
dependency-tracker-scenarios
Oct 2, 2026
Merged

ondrejmirtes merged 6 commits into
2.3.xfrom
dependency-tracker-scenarios

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

More scenarios for tracking result cache dependencies, from a survey of where popular extensions make the result cache stale.

  • Parameter closure type and closure this extensions get Scope&DependencyTracker (Pest's extend() types $this in test closures from tests/Pest.php).
  • Type-specifying extensions get Scope&DependencyTracker (webmozart/assert's implementsInterface() 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 every Pest.php. Only which files exist, not their contents (that's trackFileDependency()).
  • 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 no Scope because they describe a class - class reflection extensions adding magic methods and properties (Carbon macros, Larastan model properties, Faker providers, Magento extension attributes, Doctrine's findByX()). Inject it and track on the ClassReflection. 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 MutatingScope methods.

e2e/result-cache-value-dependency has a step for each: a closure $this extension, 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

ondrejmirtes and others added 2 commits October 2, 2026 14:53
…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
@staabm

staabm commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

oh wow. I did not imagine you would go this far with this new extension type. its really cool <3.

@ondrejmirtes

Copy link
Copy Markdown
Member Author

I'm going even further 😂 #6662

ondrejmirtes and others added 4 commits October 2, 2026 17:21
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
@canvural

canvural commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Can we also get for property extensions? In Larastan I read the database migrations when a undefined property is accessed.

@ondrejmirtes
ondrejmirtes force-pushed the dependency-tracker-scenarios branch from 6f58af6 to 2c7c9cf Compare October 2, 2026 15:29
@ondrejmirtes
ondrejmirtes merged commit ed978d4 into 2.3.x Oct 2, 2026
205 of 219 checks passed
@ondrejmirtes
ondrejmirtes deleted the dependency-tracker-scenarios branch October 2, 2026 15:29
@ondrejmirtes

Copy link
Copy Markdown
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.

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.

3 participants