Align legacy rules and reflections with current PHPStan API conventions - #1027
Merged
Conversation
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
August 5, 2026 16:20
11c49d2 to
e8cd1dd
Compare
mglaman
changed the base branch from
audit/1-correctness-fixes
to
audit/1d-autoloader-cleanup
August 5, 2026 16:44
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
August 5, 2026 16:44
e8cd1dd to
99903cc
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
August 5, 2026 17:15
99903cc to
40887b7
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
August 5, 2026 19:13
40887b7 to
6ae8385
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
August 5, 2026 19:33
6ae8385 to
1c6b5bd
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
September 8, 2026 17:42
1c6b5bd to
a15c05c
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
September 8, 2026 18:14
a15c05c to
42f177e
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
2 times, most recently
from
September 8, 2026 18:27
ee92408 to
198eec8
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
September 8, 2026 18:59
198eec8 to
f4e503b
Compare
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
September 8, 2026 19:11
f4e503b to
168c3a8
Compare
InClassNode hands the rule its ClassReflection directly, which drops the ReflectionProvider constructor dependency, the anonymous-class namespacedName juggling, and the manual FQN round-trip through ObjectType. Behavior is unchanged; the rule now simply never fires for classes PHPStan cannot reflect. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- GlobalDrupalDependencyInjectionRule: resolve the called class through $scope->resolveName() instead of requiring a FullyQualified node, and type-check against the MethodReflection interface instead of ExtendedMethodReflection, which is marked api-do-not-implement. - EntityFieldReflection: guard ReflectionProvider::getClass() with hasClass() so a missing Drupal interface degrades instead of throwing. - EntityFieldsViaMagicReflectionExtension: check interfaces via ClassReflection::implementsInterface() instead of rebuilding an ObjectType from the class name, which discarded generics and allocated on every property lookup; drop the now-unused static helpers. - FieldItemListPropertyReflection: build nullable types with TypeCombinator::addNull() instead of new UnionType. - Drop ->line($node->getStartLine()) calls that restate the default. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
implementsInterface() returns false when asked about the interface itself, so values typed as FieldItemListInterface, which is what $node->uid is, lost their magic entity and target_id properties. is() covers the interface and every implementation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mglaman
force-pushed
the
audit/2-api-conventions
branch
from
September 8, 2026 19:47
168c3a8 to
089c9d8
Compare
…constructor The constructor lookup searched the whole class body recursively, so a constructor declared by an anonymous class inside a method was taken as the plugin manager's own. With YAML discovery that reached ClassReflection::getConstructor() on a hierarchy with no constructor, which throws. Look up the class's own method and guard with hasConstructor(). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mglaman
marked this pull request as ready for review
September 8, 2026 20:23
This was referenced Sep 9, 2026
Closed
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.
Part 6 of 9 in the legacy-code audit stack (on top of #1036). Brings the oldest rules and reflection classes in line with current PHPStan 2.x API conventions and the patterns the newer code in this repo already uses.
The
getStorage()return type extension change that was in this PR moved to #1045. Review found that the union it introduced is not handled by the storage method extensions and collapses when storage classes overlap, so it needs its own tests and a design decision before it lands.What changed
PluginManagerInspectionRulemigrates fromNode\Stmt\Class_toInClassNode, which provides theClassReflectiondirectly. TheReflectionProviderconstructor dependency, anonymous-class handling, and manual FQN round-trip all disappear. Behavior is unchanged.GlobalDrupalDependencyInjectionRuleresolves the called class via$scope->resolveName()and checks against theMethodReflectioninterface instead ofExtendedMethodReflection, which PHPStan marks@api-do-not-implement. One observable change:parent::service()inside a subclass of the non-final\Drupalclass is now reported. No such subclass exists in core or this repo.EntityFieldReflectionguardsReflectionProvider::getClass()withhasClass();FieldItemListPropertyReflectionbuilds nullable types withTypeCombinator::addNull().EntityFieldsViaMagicReflectionExtensionusesClassReflection::is()for theFieldItemListInterfacecheck instead of rebuilding anObjectTypefrom the class name on every property lookup. An earlier revision usedimplementsInterface(), which returns false for the interface itself and broke$node->uid->entity. Two public static helpers that only served the old check are removed; no callers exist outside vendored copies of this repo.Redundant
->line($node->getStartLine())calls removed — that is already the default.PluginManagerInspectionRuleno longer crashes when a plugin manager without a constructor contains an anonymous class that declares one. The constructor lookup searched the class body recursively and picked up the nested one, thengetConstructor()threw on the hierarchy. It now uses the class node's own method lookup and guards withhasConstructor(). Pre-existing bug, surfaced by review.Known but out of scope
PluginManagerInspectionRuleis unreachable because the rule returns early unless the class body declares its own constructor. Tracked as a follow-up.Testing
EntityFieldsViaMagicReflectionExtensionTestgains cases for values typed asFieldItemListInterfaceitself, which is what$node->uidis. They fail on theimplementsInterface()revision and pass now. A newplugin-manager-nested-constructor.phpfixture reproduces the crash. Full suite, self-analysis, and phpcs are green.🤖 Generated with Claude Code