Skip to content

perf(joinpoint): create method and function reflection on first use - #772

Merged
lisachenko merged 2 commits into
masterfrom
feature/blissful-brahmagupta-luxio2
Oct 21, 2026
Merged

lisachenko merged 2 commits into
masterfrom
feature/blissful-brahmagupta-luxio2

Conversation

@lisachenko

@lisachenko lisachenko commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What and why

ClassFieldAccess already creates its ReflectionProperty only when someone asks for it. This PR does the same for the method and function joinpoints, so their constructors no longer build reflection:

  • DynamicTraitAliasMethodInvocation: the ReflectionFunction + ReflectionMethod pair that finds the original method body is now resolved on the first call that reaches the body (??= in proceed()), not in the constructor. getMethod() was already lazy.
  • ReflectionFunctionInvocation: stores the function name and creates the ReflectionFunction in getFunction(). The original function is called through its first-class callable, so it never needs reflection to run.

If an advice never needs reflection (for example an around advice that returns a cached value without calling proceed()), no reflection is created at all.

ReflectionConstructorInvocation was tried too and then reverted (second commit). Creating the object always needs the class and constructor reflection, and the lazy version measured 20–25% slower on the first call when the constructor runs (8 of 8 interleaved processes), plus 3% on repeated calls. It stays eager, and src/Aop/AGENTS.md notes why.

Behaviour change on internal classes: a closure without a scope class, or an unknown function name, now fails on first use instead of at construction. The existing test was updated to match, and new tests check that reflection stays lazy.

Measurements

These numbers come from a sandbox, with master and the PR loaded in one process and timed interleaved.

First call (joinpoint creation + call), around advice:

Case master PR Change
Method, around returns early 822 ns 512 ns −38%
Function, around returns early 604 ns 510 ns −16%
Method, around proceeds 971 ns 1034 ns +6% (within noise)
Function, around proceeds 656 ns 561 ns −14%

Repeated calls (composer test:performance, 5 alternating runs, median ratio against a plain call): every case matches master within ±0.3 and stays well within budget, e.g. method 10.6 → 10.9, function 10.9 → 11.1, method + around 14.2 → 14.2, function + around 16.2 → 16.3.

Checklist

  • The title follows Conventional Commits (type(scope): summary)
  • Tests cover the change
  • composer check passes: phpstan and cs are clean, and tests/Aop/Framework passes. In this sandbox, functional/console tests fail and ConstructorExecutionTransformerTest segfaults the same way on master, so these are sandbox problems. CI runs the full suite.
  • User-facing or breaking changes are in CHANGELOG.md

🤖 Generated with Claude Code

https://claude.ai/code/session_01YAJm7FNJjQyTtSYtFtJdMR

…n first use

Like ClassFieldAccess, the joinpoints no longer reflect in their constructors:
- DynamicTraitAliasMethodInvocation resolves the original-body ReflectionMethod
  on the first call that reaches it
- ReflectionFunctionInvocation creates its ReflectionFunction in getFunction()
- ReflectionConstructorInvocation creates the class and constructor reflection
  on the first proceed()/getConstructor()/getScope()

Joinpoints whose advices never need reflection (an around advice that does not
proceed) build none. Hot-path numbers are unchanged within noise.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAJm7FNJjQyTtSYtFtJdMR
@codecov

codecov Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.19%. Comparing base (0456abc) to head (fd4acdf).

Additional details and impacted files
@@             Coverage Diff              @@
##             master     #772      +/-   ##
============================================
- Coverage     98.19%   98.19%   -0.01%     
  Complexity     1910     1910              
============================================
  Files           113      113              
  Lines          4999     4995       -4     
============================================
- Hits           4909     4905       -4     
  Misses           90       90              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

…cation

Creating the object always needs the class and constructor reflection, and
the lazy version measured slower: +20-25% on the first call when the
constructor runs (8/8 interleaved processes), +3% on repeated calls.
The method and function joinpoints stay lazy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YAJm7FNJjQyTtSYtFtJdMR
@lisachenko lisachenko changed the title perf(joinpoint): create method, function and constructor reflection on first use perf(joinpoint): create method and function reflection on first use Oct 21, 2026
@lisachenko
lisachenko marked this pull request as ready for review October 21, 2026 10:52
@lisachenko
lisachenko merged commit 1fac17e into master Oct 21, 2026
14 checks passed
@lisachenko
lisachenko deleted the feature/blissful-brahmagupta-luxio2 branch October 21, 2026 10:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants