feat(aop)!: first-class callable advices - #620
Open
lisachenko wants to merge 7 commits into
Open
Conversation
Generate proxy interceptors with The::aspect() first-class callables and pass interceptor instances directly into joinpoint initialization. Format generated joinpoint initialization across multiple lines and restore default interceptor state during unserialization.
Drop the AdviceBefore/AdviceAfter/AdviceAround marker interfaces and add a required Advice::getType(): AdviceTypeEnum method instead. The new backed enum carries the advice kind (before, after, afterThrowing, around, introduction) together with its invocation priority, so joinpoint sorting and generated interceptor factory resolution no longer rely on instanceof checks against marker interfaces or a hard-coded interceptor class map. BREAKING CHANGE: the AdviceBefore, AdviceAfter and AdviceAround interfaces are removed and every Advice implementation must now implement getType(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
Advice methods discovered through attributes must now be declared public. Generated proxies reference advices as first-class callables on the aspect instance (The::aspect(SomeAspect::class)->adviceMethod(...)), which is only possible when the advice method is callable from the outside. The attribute aspect loader now fails fast with an AspectException instead of producing a proxy that would fatal at runtime. Methods holding only a #[Pointcut] attribute may stay protected/private as before. BREAKING CHANGE: aspects with protected or private advice methods are rejected during aspect loading and must make those methods public. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
Aspect-method advices stay the primary path: proxies reference them directly
as first-class callables via The::aspect(SomeAspect::class)->method(...).
Advices registered in the container as plain closures (or interceptors whose
closure is not scoped to an Aspect class) can now be woven too — the
GeneratedInterceptor descriptor marks them as container-backed and the
generated code resolves them lazily with The::advice('advisorId'), which
unwraps Advisor and AbstractInterceptor values down to the raw closure.
The joinpoint flattening now rejects non-Advice advisor values loudly instead
of guessing an aspect method from the advisor id (fromAdvisorId is removed),
and InterceptorListGenerator only accepts generated interceptor descriptors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
Add 4.0.0 changelog entries for the first-class callable advices feature and its two breaking changes (public advice methods, advice marker interfaces replaced by AdviceTypeEnum). Rework the README aspect guide to present first-class callable advices as the main approach: advice methods are public, woven directly as closures on the aspect instance via The::aspect(), with The::advice() covering container-registered closure advices. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
Update the agent guides to describe the new proxy dispatch: interceptor lists built from first-class advice callables (The::aspect()/The::advice()), the Interceptor factory facade, GeneratedInterceptor descriptors, AdviceTypeEnum and the public advice method requirement. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
lisachenko
marked this pull request as ready for review
August 30, 2026 14:44
lisachenko
commented
Aug 30, 2026
…nerators Address review feedback on #620: replace string-literal FQCNs in addUse() calls with ::class references (imports added to the four proxy generators), and give InterceptorListGenerator::generate() a documented default indent constant so call sites no longer pass a bare whitespace string. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019M9gpum1Rgasc2KtBZ3UhD
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.
What
Brings first-class callable advices into the framework as the main way advices are woven into generated code (supersedes the
feature/direct-first-class-advicesWIP branch, rebased ontomasterand reorganized into logical commits).Generated proxies now reference each advice as a closure created with first-class callable syntax directly on the aspect instance:
Advices registered in the container as plain closures (not aspect methods) are resolved lazily through the new
The::advice('advisorId')accessor, which unwrapsAdvisor/interceptor values down to the raw advice closure.Commits
The/Interceptorfacades,GeneratedInterceptordescriptors,InterceptorListGenerator, removal ofLazyAdvisorAccessor(rebased onto current master, adapting master's newer fixtures/tests).AdviceBefore/AdviceAfter/AdviceAround;Advice::getType(): AdviceTypeEnumnow carries the advice kind and its sorting priority.AspectException(methods with only#[Pointcut]keep any visibility).Adviceadvisor values loudly;GeneratedInterceptor::fromAdvisorId()removed.BC breaks
#[Before],#[After],#[Around],#[AfterThrowing]) must be public.AdviceBefore/AdviceAfter/AdviceAroundmarker interfaces removed; everyAdviceimplementation must implementgetType(): AdviceTypeEnum.Testing
./vendor/bin/phpunit— 2609 tests, 3216 assertions, green (validated on every commit of the series)../vendor/bin/phpstan analyze --memory-limit=512M— level 10, no errors on every commit.