Register each tagged service once, add PHPStan rule to keep it that way - #8228
Merged
Conversation
Two classes were tagged twice in create(): - self::BASE_PHP_DOC_NODE_VISITORS was passed to registerTagged() in two separate places, so PhpDocNodeMapper received 10 visitors instead of 5 and traversed every doc node with each visitor twice. - DynamicSourceLocatorProvider was tagged ResettableInterface explicitly, on top of the autotagging RectorConfig already applies to that interface. Both are duplicate registrations of the same singleton, so no test output changes; the work was simply done twice. Cover it with a test that asserts tagged collections hold no class twice.
…rations Guards the two mistakes fixed in this branch: - the same singleton()/tag()/registerTagged() call repeated in one method - tag() with an interface RectorConfig already autotags, on a class that is also registered via singleton() Verified against the unfixed LazyContainerFactory: the rule reports both the duplicated registerTagged(self::BASE_PHP_DOC_NODE_VISITORS) on line 613 and the redundant DynamicSourceLocatorProvider tag on line 481. The scope handed to a ClassMethod rule is the method entry, where body variables are still mixed, so the container is matched on call shape rather than on its resolved type.
The two Source fixtures exist to be read as text by RuleTestCase, so they are never instantiated and have no parent or interface, which is exactly what class-leak reports. The existing rule-test fixtures dodge this only because their names end in "Rector", which these are not.
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.
I went looking at
LazyContainerFactory::create()because it is a 320-line method, and found two duplicate registrations hiding in it. The second commit adds a PHPStan rule so they cannot come back.Every phpdoc node visitor was registered twice
self::BASE_PHP_DOC_NODE_VISITORSis passed toregisterTagged()in two separate places — once in the multi-line block, and again 18 lines later in the one-liner block:registerTagged()calls$container->tag(), which appends, so the tag collection ends up holding each class twice.PhpDocNodeMappertakes its$phpDocNodeVisitorsfrom that tag:They are the same singleton instances, so this is duplicated traversal work on every doc node of every file rather than corrupted state — the full test suite produces identical results before and after.
DynamicSourceLocatorProviderwas tagged on top of autotaggingRectorConfigalready autotagsResettableInterface($autotagInterfaces), and this class is registered viasingleton()a few lines below, so the explicit tag is a second registration. It got reset twice on everyAbstractRectorTestCaseboot. The neighbouringRenamedClassesDataCollectorline is not redundant — it is never registered throughsingleton(), so autotagging never fires for it and the explicit tag is what puts it in the collection. Only the one line is removed.Runtime test
A test asserts no tag collection holds the same class twice. It fails on both counts without this fix:
PHPStan rule
SingleServiceRegistrationRulecatches the same class of mistake at analysis time, in any registration method:It tracks
singleton(),tag()andregisterTagged()calls within one method, keyed by the arguments that identify the service — the container argument itself is excluded, soregisterTagged($rectorConfig, X, Y)compares onX, Y.Checked against the unfixed file: it reports the duplicated
registerTagged(self::BASE_PHP_DOC_NODE_VISITORS)on line 613 and the redundantDynamicSourceLocatorProvidertag on line 481. Running it over the whole codebase produces no other errors.One implementation note worth recording: the
Scopehanded to aClassMethodrule is the scope at method entry, where body variables are stillmixed. Gating on$scope->getType($rectorConfig)being aContainersilently matched almost nothing — it dropped 27 of the 30 registration calls increate()while still passing its own fixtures. The rule matches on call shape instead.Scope note
The runtime test and the PHPStan rule cover different things and both are kept: the rule sees source-level repetition in any method, the test sees the resulting container, including tags that arrive through autotagging rather than an explicit call.