Skip to content

perf: faster weave/newInstance and bind without attribute instantiation#259

Merged
koriym merged 5 commits into
ray-di:2.xfrom
koriym:codex/aop-performance-optimizations
Jul 11, 2026
Merged

perf: faster weave/newInstance and bind without attribute instantiation#259
koriym merged 5 commits into
ray-di:2.xfrom
koriym:codex/aop-performance-optimizations

Conversation

@koriym

@koriym koriym commented Jun 6, 2026

Copy link
Copy Markdown
Member

Summary

Speed up bind / weave / newInstance (construction path), complementary to #260 (per-call FCC).

  • Weaver: cache weaved FQN; new $class(...$args) instead of ReflectionClass::newInstanceArgs
  • Matchers: getAttributes(..., IS_INSTANCEOF) — no attribute newInstance() during match/bind
  • MethodMatch: skip getAnnotations(); onion order + is_a for child attributes; early-out when no annotation pointcuts
  • Compiler: same new $class(...$args) for newInstance

Rebased onto post-#260 2.x. Lazy ArrayObject / call-path work stays with #260 (this PR no longer touches ReflectiveMethodInvocation).

Microbenchmarks (pre-#260; re-run after merge if needed)

Case Before After
Weaver newInstance ~1403 ns ~463 ns (~3×)
Bind any/any 300 methods ~0.62 ms ~0.53 ms
Attribute ctors during any/any bind 300 0

Tests

  • Attribute bind does not instantiate method attributes
  • List + named constructor args via newInstance
  • Parent attribute pointcut matches child attribute
  • Annotation order → interceptor onion order

Note on $args

Public type is list<mixed>. Named-argument bags work at runtime via spread (new $class(...$args)); document that callers should pass a list or real parameter names.

Stacking

#260 call path (merged) + this PR construction path
#262 interceptor // comments — independent follow-up

Summary by CodeRabbit

  • Performance

    • Faster weaving and object creation via improved caching and direct constructor instantiation.
    • Reduced unnecessary instantiation when evaluating annotation/attribute-based bindings and matches.
  • Bug Fixes

    • Improved attribute-driven matching (including parent/child attribute relationships) and corrected interceptor ordering based on attribute precedence.
    • Fixed attribute evaluation so method attributes aren’t instantiated during matching where they shouldn’t be, and ensured serialized weavers don’t rely on in-process cache.
  • Tests

    • Added coverage for attribute matching/non-matching behavior, interceptor ordering, and constructor argument passing (positional and named).

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 845da75f-64e4-4af8-81db-04befc509e95

📥 Commits

Reviewing files that changed from the base of the PR and between fdc0c81 and deb112c.

📒 Files selected for processing (1)
  • CHANGELOG.md
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md

📝 Walkthrough

Walkthrough

Changes

Attribute matching and weaving

Layer / File(s) Summary
Native attribute matching and ordering
src/AnnotatedMatcher.php, src/Matcher/AnnotatedWithMatcher.php, src/MethodMatch.php
Annotation pointcuts use native PHP reflection attributes, support inherited attribute matching, avoid eager annotation instantiation, and preserve attribute-driven interceptor ordering.
Cached weaving and direct construction
src/Weaver.php, src/Compiler.php
Weaver caches woven class names, excludes the cache from serialization, and constructs woven classes directly with variadic arguments.
Regression fixtures, tests, and release notes
tests/BindTest.php, tests/WeaverTest.php, tests/Fake/*, CHANGELOG.md
Tests cover attribute matching, interceptor ordering, constructor arguments, and cache serialization behavior; changelog entries describe the updates.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MethodMatch
  participant ReflectionMethod
  participant AnnotatedMatcher
  participant Bindings
  MethodMatch->>ReflectionMethod: getAttributes()
  MethodMatch->>AnnotatedMatcher: match attribute names
  AnnotatedMatcher-->>MethodMatch: matching pointcuts
  MethodMatch->>Bindings: bind ordered interceptors
Loading

Possibly related PRs

  • ray-di/Ray.Aop#236: Both changes modify woven-instance initialization in Weaver::newInstance.

Suggested reviewers: ngmy

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 performance changes: faster weaving/newInstance and bind-time attribute handling without instantiation.
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.

@codecov

codecov Bot commented Jun 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (d18ecd1) to head (deb112c).

Additional details and impacted files
@@             Coverage Diff             @@
##                 2.x      #259   +/-   ##
===========================================
  Coverage     100.00%   100.00%           
- Complexity       227       234    +7     
===========================================
  Files             29        29           
  Lines            593       601    +8     
===========================================
+ Hits             593       601    +8     

☔ 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.

@koriym

koriym commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@koriym Review of draft PR #259 (performance: bind / weave / newInstance). Separate layer from #260 (per-call FCC).

Verdict

Direction is sound — keep open, rebase onto post-#260 2.x, then land.
These changes target a different cost center than #260: construction and bind-time, not intercepted-call dispatch. The numbers in the body (Weaver newInstance ~3×, attribute ctor elimination) match that story. CI was green on the last run; draft is fine until rebased.

Do not close as “done by #260”. The wins are additive in total cost:

total ≈ (#newInstance × unit from #259) + (#calls × unit from #260)

What looks good

1. Skip newInstance() on attributes during match/bind

getAnnotation() / getAnnotations() always call ReflectionAttribute::newInstance(). Matchers only need “is this attribute present (incl. subclass)?”.

Switching to:

$class->getAttributes($name, ReflectionAttribute::IS_INSTANCEOF) !== []

is the right API for that question. Tests with FakeCountingAttribute pin the contract (0 ctor calls). That’s the high-value part of the PR.

2. Weaver $classCache

Per-Weaver memo of class → aop FQN avoids repeated AopPostfixClassName + class_exists / disk load on hot newInstance loops. Weaver is already bound to one Bind + classDir, so cache key = source class name is enough. Invalidation is “new Weaver” — same as today’s bind lifecycle.

3. new $class(...$args) instead of ReflectionClass::newInstanceArgs

Same idea as #260’s spirit: drop reflection on the hot path. Finer than call path, but for DI-style mass newInstance it matters (your ~3×).

4. isset($bindings[$method]) in AopCode::addMethods

Correct and cheap. Note: #260 already has the same idea (array_flip / isset). On rebase this hunk may be empty or trivial — drop duplicate, keep one.

5. Lazy ArrayObject explicitly not in this PR

Body documents a real measurement and a revert. Good discipline. (#260 later reintroduced lazy args for the call path; that is a different trade-off and should be re-validated after stacking — see below.)

Required before merge

R1. Rebase onto current 2.x (after #260 / #261)

Branch is based on pre-FCC 2.x. Touches AopCode, Compiler — both moved under #260/#261.

R2. Constructor args: document or test ...$args vs newInstanceArgs

newInstanceArgs($args) new $class(...$args)
list 0..n positional positional
string keys values in array order (keys ignored for naming) named arguments

If any caller passes associative bags that are not parameter names, behavior can change (Error: unknown named parameter). Ray.Di / typical [] or list args are fine.

Suggestion: one unit test with a class that takes ctor args (list and, if you support it, named), and a short note in the PR body that $args must be a list or real parameter names.

R3. MethodMatch::onionOrderMatch — confirm multi-annotation order + inheritance

New nested loop:

  • outer: $method->getAttributes() (declaration order)
  • inner: pointcuts, match via === or is_a(..., true)

That preserves onion order for “bind in attribute order” and improves subclass attribute → parent pointcut key (old code keyed only exact $annotation::class). Good.

Worth an explicit test:

  1. Method with two attributes A then B → interceptors applied in A-then-B order (if both pointcuts exist).
  2. Pointcut keyed as parent annotation, method has child attribute → still binds (is_a path).

Without (2), the is_a branch is only SA-covered, not behavior-covered.

Suggestions (non-blocking)

S1. hasAnnotationPointcut early-out

Nice. When all pointcuts are non-annotation matchers, skip attribute reflection entirely. Combined with the counting test, this is the bind-time story.

S2. Nested loop complexity

O(attributes × annotation-pointcuts) vs old O(attributes) hash. Fine for normal pointcut counts; if someone registers hundreds of annotation pointcuts, hash-by-name (with a small inheritance pass) could be faster. Not needed now.

S3. assert($instance instanceof $class) moved earlier in Weaver

Moved before _setBindings. Fine; with assertions off in prod, no runtime effect. Keep.

S4. Overlap with #260

#259 item After #260
isset bindings in AopCode likely already there
skip attribute newInstance still unique and valuable
Weaver classCache + new still unique
Matcher getAttributes still unique
MethodMatch without getAnnotations() still unique

So after rebase, the PR may shrink to “bind/match without instantiating attributes + Weaver instantiate path” — still worth shipping.

S5. Stacking with #260 call path

After both land, re-measure:

Nits

  • PR title is generic (“Improve AOP performance”). Prefer something like “perf: faster weave/newInstance and bind without attribute instantiation” so it doesn’t look like a dupe of Eliminate double dispatch with parent FCC (-47% call time) #260.
  • Draft + CodeRabbit skipped — mark ready after rebase for a full bot pass.
  • Compiler / Weaver @psalm-suppress MixedMethodCall is consistent with dynamic new $class; fine.

Summary table

Area Severity Note
Rebase onto post-#260 2.x Required Conflicts expected in AopCode
$args list vs named Required (test or docs) Subtle BC edge
Onion order + is_a tests Recommended Lock inheritance match
Keep open vs close Keep open; complementary to #260
Attribute skip + Weaver cache Strength Main reason to merge

Overall: Approve the approach. Not obsolete. Rebase, add two focused tests (ctor args + annotation inheritance/order), refresh benchmarks, then undraft.

@koriym

koriym commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@koriym Full review below. As with #260, the Required / Recommended sections are self-contained instructions for the implementing agent.

Verdict

The three retained optimizations are the right ones: existence-check matching without attribute instantiation, dynamic new instead of reflection, and cached woven-class resolution. The semantics were verified against Bind/MethodMatch internals (details below). Two things block merge: a real bug in the Weaver::$classCache + serialization interaction, and sequencing with #260, which rewrites two of the same hunks and should merge first.

Verified during review — no action needed on these:

  • The assert(is_string($key)) in the new onion loop is safe. Bind::getAnnotationPointcuts() re-keys every AnnotatedMatcher pointcut by its annotation FQCN before MethodMatch ever runs, and the assert sits behind the instanceof AnnotatedMatcher guard — an integer key cannot reach it.
  • The onion phase's instanceof AnnotatedMatcher filter matches exactly the pointcuts Bind string-keys (Matcher::annotatedWith() returns AnnotatedMatcher), so nothing previously onion-matched is skipped. The only behavioral delta is a user-supplied string key on a non-annotation matcher: previously onion-bound by key coincidence, now default-bound — more correct, not less.
  • getAttributes($name, IS_INSTANCEOF) !== [] preserves post-Fix getAnnotation() to match child classes using IS_INSTANCEOF #255 getAnnotation() match semantics minus instantiation. Matchers only return bool; interceptors that need the annotation instance still get it lazily via getMethod()->getAnnotation().
  • No over-binding via is_a: after an onion-phase name match, annotatedMethodMatchBind() still runs the real class/method matchers before binding.
  • Repeatable/duplicate attributes can't double-bind — the pointcut is unset after the first match, same as before.
  • Bonus fix worth noting: old getAnnotations() instantiated every attribute on every public method at bind time, so a broken/non-instantiable unrelated attribute could fatal bind(). It no longer can. (Exception type for non-instantiable target classes changes ReflectionExceptionError with dynamic new; nothing in-repo catches ReflectionException, so no impact.)
  • The two FakeCountingAttribute regression tests pin the core claim precisely.

Required

1. Sequence behind #260 and rebase (CI here is a month stale)

This branch's merge-base is b6dd236 — before #261 and #260. The green CI ran 2026-06-07 and has not seen either. #260 merges first; then rebase onto 2.x and resolve:

2. Weaver::$classCache must not survive serialization

Weaver serialization is a supported use case (WeaverTest::testSerializedWeaverMaintainsFunctionality). As written, the private $classCache array is serialized with the object. Unserialized in a fresh process, weave() returns the cached FQCN immediately — skipping the class_exists/loadClass() steps the old code performed on every call — so the generated file is never required and new $aopClass(...) fatals with "Class not found". The in-process test can't catch this because the classes are already loaded.

Fix (either):

  • public function __sleep(): array returning all property names except classCache (precedent: Bind::__sleep()), or
  • public function __wakeup(): void { $this->classCache = []; }

Optionally strengthen the serialization test: after unserialize(), assert via reflection that classCache is empty, then call newInstance() again.

3. CHANGELOG.md — add Unreleased entries (convention per #260)

  • Changed: matcher/bind checks no longer instantiate attributes (existence checks via getAttributes(..., IS_INSTANCEOF)); bind() no longer fails when an unrelated method attribute is non-instantiable. Weaver/Compiler instantiate proxies with dynamic new instead of reflection (~3x faster newInstance). Weaver caches woven-class resolution per instance. Annotation-order (onion) binding now honors attribute inheritance consistently with Fix getAnnotation() to match child classes using IS_INSTANCEOF #255 — interceptor order for subclass-annotated methods may change (previously such pointcuts bound in the default phase).

Recommended

4. Add an ordering test for the inheritance path

The is_a() subclass branch in onionOrderMatch() is the one new semantic path without a dedicated test (existing #255 tests only verify that binding happens, not its order). Suggested: a method annotated #[FakeChildAttr] #[FakeOtherAttr] where FakeChildAttr extends FakeBaseAttr, pointcuts registered for FakeBaseAttr and FakeOtherAttr in reverse order, assert interceptor invocation order follows attribute declaration order.

5. Refresh PR body after rebase

Re-measure the 3.03x Weaver::newInstance figure on the post-#260 base (the instantiation path is untouched by #260, so it should hold — but the "Before" column no longer describes 2.x). Drop the "Psalm 6.13.1 crashes on PHP 8.5" validation note if rebased CI is green.

Nits (optional)

  • hasAnnotationPointcut() is recomputed per method inside a per-class bind(); it could be computed once per __invoke… per bind call. Micro — fine to skip.
  • assert(is_string($key)); + /** @var class-string $key */ double annotation: the @var is what convinces static analysis; the assert is a dev-time guard. Fine as is.

Overall acceptance: rebase onto post-#260 2.xcomposer tests green locally → full CI matrix green on the rebased head → un-draft.

koriym added 3 commits July 12, 2026 01:56
Keep ray-di#260 ReflectiveMethodInvocation/AopCode. Add tests for list/named
ctor args, parent attribute match, annotation onion order. CHANGELOG.
@koriym
koriym force-pushed the codex/aop-performance-optimizations branch from 92e0eab to f57adb7 Compare July 11, 2026 16:57
@koriym koriym changed the title Improve AOP performance perf: faster weave/newInstance and bind without attribute instantiation Jul 11, 2026
@koriym
koriym marked this pull request as ready for review July 11, 2026 16:57
koriym added 2 commits July 12, 2026 02:05
Restored Weavers must re-load weaved class files; a stale FQN cache
would skip require and fatal in a new process. Document bind-time
attribute safety in CHANGELOG.
Document that annotation-order bind now considers attribute inheritance
(aligned with ray-di#255); subclass attributes may change interceptor order.
@koriym

koriym commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@CodeRabbit approve?

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

#!/bin/bash gh pr view 259 --repo ray-di/Ray.Aop --json state,isDraft,mergeable,baseRefName,headRefName,commits,mergeStateStatus,statusCheckRollup -q '.' echo "---" gh pr view 260 --repo ray-di/Ray.Aop --json state,merged,mergedAt echo "---checks---" gh pr checks 259 --repo ray-di/Ray.Aop

@koriym

koriym commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai approve?

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== PR 259 ==="
gh pr view 259 --repo ray-di/Ray.Aop --json state,isDraft,mergeable,baseRefName,headRefName,mergeStateStatus,commits -q '{state,isDraft,mergeable,baseRefName,headRefName,mergeStateStatus}'
echo "=== PR 259 checks ==="
gh pr checks 259 --repo ray-di/Ray.Aop || true
echo "=== PR 260 ==="
gh pr view 260 --repo ray-di/Ray.Aop --json state,merged,mergedAt,baseRefName -q '{state,merged,mergedAt,baseRefName}'
echo "=== PR 259 files changed ==="
gh pr diff 259 --repo ray-di/Ray.Aop --name-only

Length of output: 3786


🏁 Script executed:

#!/bin/bash
set -uo pipefail
echo "=== PR 260 ==="
gh pr view 260 --repo ray-di/Ray.Aop --json state,mergedAt,baseRefName -q '{state,mergedAt,baseRefName}'
echo "=== PR 259 files changed ==="
gh pr diff 259 --repo ray-di/Ray.Aop --name-only
echo "=== Does 2.x contain AopCode.php with array_flip (post-260 marker)? ==="
gh api repos/ray-di/Ray.Aop/contents/src/AopCode.php?ref=2.x --jq '.content' | base64 -d | grep -n "array_flip\|isset(\$bindings" 
echo "=== PR 259 merge base vs 2.x head ==="
gh pr view 259 --repo ray-di/Ray.Aop --json commits -q '.commits[-1].oid'
git -C . rev-parse HEAD 2>/dev/null || true

Length of output: 1586


@koriym Verified the current state of #259 directly:

  • Base is already post-Eliminate double dispatch with parent FCC (-47% call time) #260: PR #260 merged into 2.x on 2026-07-11, and #259's head commit (deb112c) matches 2.x's current head exactly — the rebase you required is already done.
  • Mergeable/clean: mergeStateStatus: CLEAN, mergeable: MERGEABLE, not a draft.
  • Overlap resolved: #259's diff no longer touches src/AopCode.php — confirmed 2.x's AopCode.php still has #260's array_flip(array_keys($bind->getBindings())) + isset($bindings[$methodName]) lookup, so the duplicate hunk you flagged was dropped in favor of #260's version, as required.
  • Weaver::$classCache serialization bug (R2): fixed — __sleep() now explicitly lists only non-cache properties, and WeaverTest was updated to assert the serialized payload excludes classCache and that it's empty post-unserialize().
  • CHANGELOG.md (R3): Unreleased entries added covering attribute-safe matching/bind, dynamic instantiation, Weaver caching, and the annotation-order/inheritance behavior change.
  • CI: all green — PHPUnit 8.2–8.5 (ubuntu + windows), cs, PHPStan, Psalm, PHPMD, ComposerRequireChecker, mutation testing, codecov patch/project.
  • Recommended (non-blocking) items from my earlier review — the explicit inheritance-ordering test and a refreshed benchmark note in the PR body — are nice-to-haves and don't block merge.

Everything required is in place and CI is clean on the rebased head. I'm approving.

[approve]

ᕮ(•ᴥ•)ᕭ

@koriym
koriym merged commit 1b76433 into ray-di:2.x Jul 11, 2026
18 checks passed
@koriym
koriym deleted the codex/aop-performance-optimizations branch July 11, 2026 17:25
@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
gh pr view 259 --repo ray-di/Ray.Aop --json state,isDraft,mergeable,baseRefName,headRefName,mergeStateStatus,statusCheckRollup -q '.'
echo "---pr260---"
gh pr view 260 --repo ray-di/Ray.Aop --json state,merged,mergedAt
echo "---checks---"
gh pr checks 259 --repo ray-di/Ray.Aop

Length of output: 8870


@koriym The PR is already merged! 🎉 gh pr view 259 shows "state":"MERGED" on base 2.x, and all CI checks (PHPUnit across PHP 8.2–8.5, static analysis, mutation testing, coding standards, codecov) came back green. Since it's merged, there's nothing left to approve — the @coderabbitai approve action only applies to open PRs.

Congrats on getting it landed!

ヽ(•‿•)ノ

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -x
gh pr view 259 --repo ray-di/Ray.Aop --json state,isDraft,mergeable,baseRefName,headRefName,mergeStateStatus,commits -q '{state,isDraft,mergeable,baseRefName,headRefName,mergeStateStatus}'
echo "---pr260---"
gh pr view 260 --repo ray-di/Ray.Aop --json state,merged,mergedAt -q '.'
echo "---checks---"
gh pr checks 259 --repo ray-di/Ray.Aop
echo "---weaver classCache/wakeup---"
gh pr diff 259 --repo ray-di/Ray.Aop -- src/Weaver.php | head -100
echo "---changelog diff---"
gh pr diff 259 --repo ray-di/Ray.Aop -- CHANGELOG.md

Length of output: 4425


🏁 Script executed:

#!/bin/bash
gh pr view 260 --repo ray-di/Ray.Aop --json state,closed,mergedAt,baseRefName -q '.'
echo "---diff weaver---"
gh pr diff 259 --repo ray-di/Ray.Aop > /tmp/pr259.diff
grep -n "classCache\|__sleep\|__wakeup" -A5 -B5 /tmp/pr259.diff | sed -n '1,150p'
echo "---changelog---"
grep -n "Unreleased" -A40 /tmp/pr259.diff | grep -A40 "CHANGELOG" | head -80
awk '/diff --git a\/CHANGELOG.md/,/diff --git a\/src/' /tmp/pr259.diff | head -100
</shell>

Length of output: 6444


@koriym Confirmed — this looks ready:

  • #260 merged (2026-07-11) and #259 is rebased onto post-Eliminate double dispatch with parent FCC (-47% call time) #260 2.x (mergeStateStatus: CLEAN, mergeable: MERGEABLE).
  • Weaver::$classCache serialization bug fixed: __sleep() now returns only ['bindName', 'compiler', 'bind', 'classDir'], excluding classCache. WeaverTest::testSerializedWeaverMaintainsFunctionality was strengthened exactly as recommended — asserts the serialized payload doesn't contain classCache, then verifies via ReflectionProperty that it's empty ([]) after unserialize().
  • CHANGELOG.md has the required Unreleased entries: faster weave/newInstance + attribute-free matching, onion-order inheritance semantics (aligned with #255), and the two Fixed entries (attribute-instantiation-avoidance bug, Weaver serialization exclusion) — all tagged (perf: faster weave/newInstance and bind without attribute instantiation #259).
  • CI is fully green across the matrix (PHPUnit 8.2–8.5 incl. Windows, Coding Standards, PHPStan/Psalm/PHPMD/ComposerRequireChecker, mutation testing, Codecov).

All required items from the review are addressed. Approving now.

[approve]

♡(◡‿◡✿)


✏️ Learnings added
Learnt from: koriym
URL: https://github.com/ray-di/Ray.Aop/pull/259

Timestamp: 2026-07-11T17:26:00.026Z
Learning: In ray-di/Ray.Aop, `Weaver` (src/Weaver.php) caches woven class FQNs per-instance in a private `$classCache` array keyed by source class name, populated in `weave()`. This cache must be excluded from serialization via `__sleep(): array` (returning only `['bindName', 'compiler', 'bind', 'classDir']`), otherwise a Weaver restored via `unserialize()` in a fresh process would return a cached FQN from `weave()` without re-running `loadClass()`/`compile()`, causing `newInstance()` to fatal because the class file was never `require`d.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

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.

1 participant