feat: Support first-class callable syntax for proxy method/function invocations#566
feat: Support first-class callable syntax for proxy method/function invocations#566
Conversation
- DynamicTraitAliasMethodInvocation: accept optional ?Closure $callable; when provided, use Closure::call() to rebind $this on every invocation instead of ReflectionMethod::invokeArgs(). Falls back to reflection for backward compat. - StaticTraitAliasMethodInvocation: accept optional ?Closure $callable; wraps it in a forward_static_call shim so bindTo(null, $scope) correctly forwards late-static-binding class. Falls back to reflection for backward compat. - ReflectionFunctionInvocation: accept optional ?Closure $callable; when provided, calls it directly instead of ReflectionFunction::invokeArgs(). - InterceptorInjector: add optional $callable parameter to forMethod(), forStaticMethod(), and forFunction(); passes it through to invocation ctors. - ClassProxyGenerator: pass $this->__aop__method(...) or self::__aop__method(...) as 4th arg when method is declared in the proxied class (has a trait alias). Inherited methods continue to use reflection (parent:: first-class callables cannot be rebound via Closure::call() in PHP). - TraitProxyGenerator / EnumProxyGenerator: always pass trait-alias callable since all intercepted methods in traits/enums have __aop__ aliases. - FunctionProxyGenerator: pass \functionName(...) (global function reference) as 3rd arg to avoid calling namespace-scoped proxy recursively. - Update all proxy snapshot fixture files. - Add new tests for callable rebinding, LSB, and function invocation paths. - Add TraitAliasProxy helpers and TraitAliasProxied::getObjectId stub method. Agent-Logs-Url: https://github.com/goaop/framework/sessions/83ea73e0-1642-4248-a97d-ff448505cab4 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
Agent-Logs-Url: https://github.com/goaop/framework/sessions/83ea73e0-1642-4248-a97d-ff448505cab4 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
Agent-Logs-Url: https://github.com/goaop/framework/sessions/399178e5-1968-4337-992f-a1a469792932 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
…asMethodInvocation, simplify docblocks, use parent::method(...) for inherited method callables Agent-Logs-Url: https://github.com/goaop/framework/sessions/2e78dd0a-1006-4727-8e55-b4e67db11722 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
…ix docblock wording in InterceptorInjector and ReflectionFunctionInvocation Agent-Logs-Url: https://github.com/goaop/framework/sessions/2e78dd0a-1006-4727-8e55-b4e67db11722 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
|
@copilot phpstan is failing. Also, I haven't seen any examples in generated proxies where |
…ent::method(...) assertions for inherited proxy generation Agent-Logs-Url: https://github.com/goaop/framework/sessions/6547dd4b-e999-43fd-ae7c-fa5f96aa9720 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
Fixed in d26b543: PHPStan: The error was a pre-existing Generated proxy examples with
|
The framework previously relied on
ReflectionMethod::invokeArgs/ReflectionFunction::invokeArgsandClosure::bindtricks inside invocation constructors to locate and call original methods. This adds first-class callable support, passing the original method/function reference directly into each joinpoint constructor as a required parameter.Invocation classes
All
$closureToCallparameters are now required (non-nullable) — this is a BC break for all invocation class constructors.AbstractMethodInvocation: gains a requiredClosure $closureToCallparameter (4th argument) stored asprotected readonly. Child classes may wrap or rebind it as needed.DynamicTraitAliasMethodInvocation: stores the callable in the parent but dispatches viaReflectionMethod::invokeArgs($this->instance, $this->arguments)for reliability and performance. Internally resolves aprivate readonly ReflectionMethod $originalMethodToCall— the__aop__alias for own methods, orgetPrototype()for inherited ones.ReflectionMethod::invokeArgsis used becauseClosure::call()has known issues with pass-by-reference arguments (PHP bug #72326) andReflectionMethod::invokeis surprisingly fast.StaticTraitAliasMethodInvocation: wraps the callable in astatic fn(array $args) => forward_static_call($callable, ...$args)shim stored in$closureToCall;bindTo(null, $scope)forwards the correct LSB class per call.ReflectionFunctionInvocation: calls the callable directly via($this->closureToCall)(...$this->arguments)inproceed().InterceptorInjector
forMethod(),forStaticMethod(), andforFunction()each require aClosure $closureToCallparameter that is forwarded to the respective invocation constructor.Proxy generators
Generated proxy method bodies now always pass a first-class callable as the 4th argument:
Tests
testReflectionDispatchCallsEachInstanceCorrectly: verifies that the static singleton joinpoint correctly dispatches to each distinct caller instance via reflection.testDispatchInvokesOriginalMethodBody: asserts that dispatch routes through the__aop__alias body, not the overridden public method.testInheritedMethodDispatchCallsEachInstanceCorrectly: verifies that inherited methods (no trait alias) dispatch correctly viagetPrototype().testFirstClassCallableLsbWithSubclassScope: verifies LSB is forwarded correctly when the static callable is invoked from a subclass scope.ReflectionFunctionInvocationTest: covers the callable path for function interception.testGenerateProxyForInheritedMethodDoesNotCreateTraitAlias(extended): asserts that the generated proxy for an inherited instance method containsparent::method(...)as the first-class callable.testGenerateProxyForInheritedStaticMethodUsesParentCallable(new): asserts that the generated proxy for an inherited static method containsparent::method(...)as the first-class callable and no__aop__alias, e.g.: