Skip to content

Deduplicate aspect classes on repeated registration#418

Merged
binaryfire merged 5 commits into
hypervel:0.4from
anabeto93:fix/aspect-collector-duplicate-registration
Jul 6, 2026
Merged

Deduplicate aspect classes on repeated registration#418
binaryfire merged 5 commits into
hypervel:0.4from
anabeto93:fix/aspect-collector-duplicate-registration

Conversation

@anabeto93

@anabeto93 anabeto93 commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

AspectCollector::setAround() merges class rules with array_merge(), so registering the same aspect again appends duplicate entries. Providers re-register their aspects on every application boot, and each duplicate makes GenerateProxies re-match a longer rule list against the class map on the next boot.

In a test suite that boots the application per test this becomes quadratic: on a 458-test suite with the Sentry and Telescope Guzzle aspects registered, per-test boot grew from ~0.05s on the first test to ~1.3s by the last. With deduplication the growth is gone.

AfterEachTestSubscriber already flushes the collector between tests; this makes the registration itself idempotent so repeated boots within one process stay bounded.

Test run:

vendor/bin/phpunit tests/Di/Aop/AspectCollectorTest.php
OK (13 tests, 21 assertions)

Summary by CodeRabbit

  • Bug Fixes

    • Improved how class-based rules are applied, making aspect and proxy behavior more consistent across matching flows.
    • Fixed duplicate class registrations so repeated setup no longer creates redundant entries.
  • Tests

    • Expanded coverage for aspect registration, rule matching, and proxy-related behavior to reflect the updated handling.

AspectCollector::setAround() merges class rules with array_merge(), so
registering the same aspect again appends duplicate entries. Providers
re-register their aspects on every application boot, and each duplicate
makes GenerateProxies re-match a longer rule list against the class map
on the next boot. In a test suite that boots the application per test,
boot time grows linearly with the number of tests already run.

Deduplicate the merged class list so repeated registration is
idempotent.
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

AspectCollector's aspect metadata storage is consolidated from a dual container/aspectRules structure into a single $aspectRules map, with setAround(), forgetAspect(), getClassRules(), getRules(), and flushState() reworked accordingly. Public get(), set(), and list() methods are removed. Consumers (Aspect, ProxyManager, ProxyTrait, InteractsWithAop) switch to getClassRules(). Tests are updated for signatures and new setAround deduplication behavior.

Changes

AspectCollector Storage Refactor and Consumers

Layer / File(s) Summary
AspectCollector internal storage refactor
src/di/src/Aop/AspectCollector.php
Removes $container and its Arr dependency, reworks setAround() to merge/deduplicate classes directly in $aspectRules, and updates forgetAspect(), getClassRules(), getRules(), flushState() to use only $aspectRules; removes get(), set(), list().
Consumers switch to getClassRules()
src/di/src/Aop/Aspect.php, src/di/src/Aop/ProxyManager.php, src/di/src/Aop/ProxyTrait.php, src/foundation/src/Testing/Concerns/InteractsWithAop.php
Replaces AspectCollector::get('classes', []) calls with AspectCollector::getClassRules(); Aspect::parseClasses() iterates the nested rule collection directly instead of re-fetching per-aspect rules.
Test updates for new API
tests/Di/Aop/AspectCollectorTest.php, tests/Di/Aop/ProxyTraitTest.php
Adds : void return types across tests, adds new tests for setAround() deduplication, removes tests for removed get()/list() methods, and switches fixture setup from AspectCollector::set('classes', ...) to AspectCollector::setAround(...).

Estimated code review effort: 2 (Simple) | ~15 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: making repeated aspect registration deduplicate class rules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown

Greptile Summary

This PR eliminates quadratic boot-time growth in test suites (and long-lived workers) by making AspectCollector::setAround() idempotent: it now deduplicates class rules with array_unique on every call instead of blindly appending. Alongside the deduplication fix, the PR removes the redundant $container static property and its get()/set()/list() public API, leaving $aspectRules as the single source of truth exposed through a new getClassRules() method.

  • setAround() now always applies array_values(array_unique(array_merge($existing, $classes))), covering both the initial-registration path and repeated re-registrations, and eliminating the divergence that existed between $container and $aspectRules in the old code.
  • Aspect::parseClasses() is simplified to iterate directly over the getClassRules() map, removing a redundant per-aspect re-lookup via getRule().
  • Tests in ProxyTraitTest are migrated from the removed AspectCollector::set() to setAround(), and two new deduplication tests are added.

Confidence Score: 5/5

Safe to merge — the change is self-contained, the previous divergence between the two data stores is eliminated, and deduplication is now exercised on every registration path.

The previous comment identified that the else (initial registration) path did not deduplicate while the re-registration path did. The new code removes that distinction entirely — there is one unconditional path that always applies array_unique. The $container secondary store and its public get()/set()/list() methods are removed, eliminating the source of divergence. All existing callers in the framework have been migrated to getClassRules(), and a grep confirms no remaining usages of the old API. New tests cover both the first-registration and repeated-registration deduplication cases.

No files require special attention.

Important Files Changed

Filename Overview
src/di/src/Aop/AspectCollector.php Core fix: removes $container redundancy, collapses if/else into a single always-deduplicating setAround(), and introduces getClassRules() as the clean replacement for the removed get('classes', []) pattern.
src/di/src/Aop/Aspect.php parseClasses() simplified to iterate directly over getClassRules() values, removing the redundant per-aspect getRule() lookup; logic is equivalent.
src/di/src/Aop/ProxyManager.php Two call sites updated from AspectCollector::get('classes', []) to getClassRules(); no logic change.
src/di/src/Aop/ProxyTrait.php Single call site updated from get('classes', []) to getClassRules(); no logic change.
src/foundation/src/Testing/Concerns/InteractsWithAop.php resolveMatchingAspects() updated from get('classes', []) to getClassRules(); no logic change.
tests/Di/Aop/AspectCollectorTest.php Two new deduplication tests added; tests for the removed get()/set()/list() API replaced with equivalent getClassRules() coverage; return type hints added to all test methods.
tests/Di/Aop/ProxyTraitTest.php Tests migrated from the removed AspectCollector::set() to setAround(); return type hints added to all test methods.

Reviews (2): Last reviewed commit: "Update AOP collector tests for single st..." | Re-trigger Greptile

AspectCollector kept class-targeting rules in two stores: the generic dot-key container and the priority-aware aspect rules array. Re-registering an aspect had to keep both stores in sync, which made duplicate class rules easy to accumulate and made the proxy generator scan the same rule repeatedly against the class map.

Make the aspect rules array the single source of truth, add a typed getClassRules() reader for consumers that only need aspect => class rules, and remove the old generic get/set/list metadata surface. setAround() now merges and deduplicates class rules once while preserving priority overwrite behavior on repeated registration.

Update the AOP parser, proxy manager, and proxy trait to read the typed class-rule map. Also remove the redundant getRule() re-fetch in Aspect::parseClasses() now that the passed map is already the canonical class-rule data.
InteractsWithAop manually resolves matching aspects for tests that need to run the AOP pipeline without generated proxies. That helper was still reading AspectCollector through the removed generic classes metadata store.

Switch it to AspectCollector::getClassRules() so the helper follows the same single-source class-rule API as the runtime proxy path. The returned shape is unchanged for the helper: aspect class names keyed to their class-targeting rule lists.
The collector no longer exposes the old generic dot-key metadata helpers, so tests should describe the actual supported AOP registry behavior instead of the removed storage mechanism.

Add coverage for duplicate class rules on both initial registration and repeated registration. Assert class-rule reads through getClassRules(), and keep existing merge, priority, forget, and flush behavior covered.

Update ProxyTraitTest to register aspects through setAround() instead of seeding the collector internals directly. This exercises the real registration path while preserving the default zero-priority behavior those tests relied on.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/Di/Aop/AspectCollectorTest.php (1)

28-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider covering priority behavior on duplicate setAround() calls.

The new dedup tests correctly verify that class arrays are deduplicated on both initial registration (lines 46-54) and repeated registration (lines 56-65). However, none of these tests assert what happens to priority when setAround() is called twice for the same aspect with differing priority values (e.g., does the second call overwrite the priority, or is the original preserved?). Since the PR's core change is around repeated registration becoming idempotent, locking in this contract with a test would help prevent regressions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Di/Aop/AspectCollectorTest.php` around lines 28 - 65, Add a test in
AspectCollectorTest that exercises duplicate setAround() calls with different
priority values and asserts the expected priority behavior. Use
AspectCollector::setAround(), getPriority(), and getRule() to verify whether the
second registration preserves or overwrites the existing priority, alongside the
existing class deduplication behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/Di/Aop/AspectCollectorTest.php`:
- Around line 28-65: Add a test in AspectCollectorTest that exercises duplicate
setAround() calls with different priority values and asserts the expected
priority behavior. Use AspectCollector::setAround(), getPriority(), and
getRule() to verify whether the second registration preserves or overwrites the
existing priority, alongside the existing class deduplication behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e507cb47-bd9b-49a8-a362-2e6475ceb4d2

📥 Commits

Reviewing files that changed from the base of the PR and between 96d6c66 and e845966.

📒 Files selected for processing (7)
  • src/di/src/Aop/Aspect.php
  • src/di/src/Aop/AspectCollector.php
  • src/di/src/Aop/ProxyManager.php
  • src/di/src/Aop/ProxyTrait.php
  • src/foundation/src/Testing/Concerns/InteractsWithAop.php
  • tests/Di/Aop/AspectCollectorTest.php
  • tests/Di/Aop/ProxyTraitTest.php

@binaryfire

Copy link
Copy Markdown
Collaborator

@anabeto93 Thanks!

@binaryfire binaryfire merged commit 4b47c67 into hypervel:0.4 Jul 6, 2026
36 checks passed
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