Eliminate double dispatch with parent FCC (-47% call time) - #260
Conversation
|
Warning Review limit reached
Next review available in: 21 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughGenerated proxies now dispatch through ChangesProxy weaving and interception
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Compiler
participant AopCode
participant ReflectiveMethodInvocation
Compiler->>AopCode: generate intercepted method body
AopCode->>ReflectiveMethodInvocation: construct invocation with arguments and parentCall
ReflectiveMethodInvocation->>ReflectiveMethodInvocation: proceed through the bound callable
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 2.x #260 +/- ##
===========================================
Coverage 100.00% 100.00%
+ Complexity 228 227 -1
===========================================
Files 29 29
Lines 584 593 +9
===========================================
+ Hits 584 593 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@koriym Full review below. The Required / Recommended sections are written as self-contained instructions for the implementing agent — they can be executed without re-deriving the analysis. VerdictThe architecture is sound. Removing Verified during review — no action needed on these:
Required — CI is currently red1. Psalm: 4 errors in
|
- Replace _intercept() indirection with direct parent::method(...) FCC in generated code, removing the _isAspect recursion guard entirely - Lazy-create ArrayObject in ReflectiveMethodInvocation (hot path avoids it) - Replace call_user_func_array with first-class callable spread - Replace in_array O(n*m) with isset O(1) in AopCode::addMethods - Remove dead _isAspect flag and _intercept() from both traits - Add Closure $parentCall parameter to ReflectiveMethodInvocation (BC-safe) - Remove stale tmp_unerase fixture referencing removed _intercept() Benchmark: 2.99μs → 1.58μs (-47%), overhead 19.7x → 9.9x Tests: 164/164 pass
- Add AopCode::GENERATION constant to hash for cache invalidation - Replace preg_replace in insert() with strrpos+substr_replace - Fix nowdoc template indentation and remove unnecessary escapes - Remove unused in_array import, fix psalm annotations - Deprecate InterceptTraitState::$isAspect - Remove unused psalm-import-type from traits - Delete outdated method_stmts_gen.php dev script - Add regression test: reentrant interceptor cross-method call - Add regression test: readonly class method interception - Expand FakePhp82ReadOnlyClass with greet() method
- Remove unused ReflectiveMethodInvocation::resetArgs() - Remove dead InterceptTraitState::$isAspect - Change AopCode::resolveInterceptTrait() from public to private
- Fix CS: one-line doc comment, blank line before function, early exit - Fix PHPStan: assign.propertyType + callable.nonCallable ignores - Add .gitkeep to tests/tmp_unerase/ for CI directory creation - Add regenerated fixture with new codegen pattern
MethodSignatureString was emitting \\ for attribute class names
and enum argument values, which was masked by preg_replace's
replacement-string backslash collapsing in the old insert().
The new strrpos+substr_replace exposes the double-escape,
causing ParseError for attributes with namespaced class/enum args.
- formatAttributeStr: sprintf('\\\\%s') → sprintf('\\%s')
- formatArg: remove redundant '\\\\' prefix before var_export
- Fix orphaned docblock on insert()
- testVariousMethodSignaturesInPhp81 now passes (was ParseError)
… fix typos - Remove @codeCoverageIgnore on Weaver::newInstance line that is covered by testNewInstanceWithoutBindingsReturnsOriginalClass - Remove unused symfony/string from require-dev - Fix addInterceporTrait → addInterceptorTrait (missing 't') - Fix Genaerates → Generates in Compiler docblock - Fix modifered → modified in MethodSignatureString
Required: branch parentCall assignment for Psalm, ignore unreachable insert() guard for codecov. Recommended: gitignore generated proxies, $1 attribute regression test, vacuous empty-bindings assertion, Unreleased CHANGELOG for parent FCC. Nits: bench iteration comment, generated newline, micro_bench cleanup. Also fix invalid trailing comma in composer.json.
7c42b6d to
e0dce35
Compare
- Pin AopPostfixClassName hash formula (order + GENERATION) - Assert empty-bindings codegen keeps trailing newline (insert skip) - hasNoBinding uses hasBoundMethod only; update tests accordingly
Ad-hoc measurement scripts for the FCC PR; not used by CI or the library.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/Compiler.php (1)
115-128: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueConsider early-return in
hasBoundMethodfor efficiency.The loop sets
$hasMethod = truebut continues iterating all remaining bindings unnecessarily. An earlyreturn trueon first match would be more efficient, especially with many bindings.♻️ Proposed refactor
foreach ($bindingMethods as $bindingMethod) { if (! method_exists($class, $bindingMethod)) { continue; } - $hasMethod = true; + return true; } - return $hasMethod; + return false;🤖 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 `@src/Compiler.php` around lines 115 - 128, Update hasBoundMethod to return true immediately when method_exists finds the first matching binding method, and remove the now-unnecessary mutable $hasMethod state; retain the final false result when no binding methods exist on the class.
🤖 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.
Inline comments:
In `@src/ReflectiveMethodInvocation.php`:
- Around line 56-63: Update manual construction of ReflectiveMethodInvocation,
especially in FakeWeaved, to pass the appropriate parent::method(...) callable
as $parentCall. Ensure the null-$parentCall fallback in
ReflectiveMethodInvocation is not used by these callers, preventing re-entry
into overridden methods while preserving generated proxy behavior.
---
Nitpick comments:
In `@src/Compiler.php`:
- Around line 115-128: Update hasBoundMethod to return true immediately when
method_exists finds the first matching binding method, and remove the
now-unnecessary mutable $hasMethod state; retain the final false result when no
binding methods exist on the class.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a418aeab-41dc-4cdb-a255-20763a944e30
📒 Files selected for processing (21)
.gitignoreCHANGELOG.mdcomposer.jsonsrc/AopCode.phpsrc/AopPostfixClassName.phpsrc/Compiler.phpsrc/InterceptTrait.phpsrc/InterceptTraitState.phpsrc/MethodSignatureString.phpsrc/ReadOnlyInterceptTrait.phpsrc/ReflectiveMethodInvocation.phpsrc/Weaver.phptests/AopCodeTest.phptests/AopPostfixClassNameTest.phptests/CompilerTest.phptests/Fake/FakePhp82ReadOnlyClass.phptests/Fake/FakePhp8Types.phptests/Fake/FakeReentrantInterceptor.phptests/script/method_stmts_gen.phptests/tmp_unerase/.gitkeeptests/tmp_unerase/Ray_Aop_FakeWeaverMock_1899453090.php
💤 Files with no reviewable changes (4)
- tests/tmp_unerase/Ray_Aop_FakeWeaverMock_1899453090.php
- src/InterceptTraitState.php
- tests/script/method_stmts_gen.php
- src/ReadOnlyInterceptTrait.php
The property is the legitimate readonly store for generated proxies; initialize via _setBindings, then read only.
Two-statement story (build MethodInvocation, then proceed). Name matches interceptor API. Blank line before proceed/return aligns with common CS so accidental formatters do not churn generated files. Bump GENERATION.
sprintf glued " }" onto proceed();. rtrim body and newline before brace. Bump GENERATION to 4.
- Two-line \$invocation dispatch without blank line before proceed - Trait use blank line, class brace spacing, EOF newline - Method body 8-space indent; signatures 4-space indented - Postfix is unsigned crc32 digits only (ValidClassName / no leading _) - Bump GENERATION to 5
Keep ray-di#260 ReflectiveMethodInvocation/AopCode. Add tests for list/named ctor args, parent attribute match, annotation onion order. CHANGELOG.
Summary
Eliminates double-dispatch bottleneck: replaces
_intercept()indirection with directparent::method(...)FCC in generated proxy code. Removes_isAspectrecursion guard,call_user_func_array, and one full trip through the proxy per intercepted call.Why this matters
This is the deepest fixed cost of Ray.Aop: every intercepted call pays it, and nothing above the library can amortize it away. The win is not end-user “feel” of a single request — it is unit cost of the dispatch layer.
getThis()no longer corrupts_isAspectinto infinite recursion;$1-style attribute args are no longer eaten bypreg_replacebackreferences.Benchmark
Scripts are not kept on this branch (removed as one-off measurement tools). To re-run if needed, check out the commit that still has them and run
php benchmarks/bench.php:bench.php: https://github.com/koriym/Ray.Aop/blob/fb405ca088bbaae90d01b5011aa26ee942db79ac/benchmarks/bench.phpNumbers depend on machine / PHP build; treat as relative before/after on the same host. Absolute µs are less important than the overhead-ratio change above.
Key Changes
parent::METHOD(...)FCC$parentCall(BC-safe)_isAspectflag and_intercept()(28→20 lines)AopCode::GENERATIONfor cache invalidationinsert():preg_replace→strrpos+substr_replace(fixes backreference bug)addMethods():in_arrayO(n*m) →issetO(1)Tests
PHPUnit green. Added reentrant interceptor and readonly class interception tests, plus
$1attribute codegen regression.Review follow-up
Addressed review feedback: Psalm/PHPStan on
ReflectiveMethodInvocation, codecov ignore for unreachableinsert()guard, gitignore for generated tmp proxies, CHANGELOG Unreleased, regression tests, mutation escapes on hot paths.Note
CodeRabbit skipped while this PR was draft; re-run after ready-for-review.