feat: Support first-class callable syntax for proxy method/function invocations#566
Conversation
|
@copilot phpstan is failing. Also, I haven't seen any examples in generated proxies where |
Fixed in d26b543: PHPStan: The error was a pre-existing Generated proxy examples with
|
d26b543 to
6510c45
Compare
- DynamicTraitAliasMethodInvocation: accept Closure $closureToCall; uses ReflectionFunction around it to reconstruct ReflectionMethod; calls ReflectionMethod->invokeArgs to support by-reference parameters in methods - StaticTraitAliasMethodInvocation: accept Closure $closureToCall; wraps it in a forward_static_call_array shim so bindTo(null, $scope) correctly forwards late-static-binding class. - ReflectionFunctionInvocation: accept Closure $closureToCall; calls it directly instead of ReflectionFunction::invokeArgs(). - InterceptorInjector: add required $closureToCall 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). - 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. Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
6510c45 to
4361f06
Compare
|
@copilot add a test for function interception to correctly pass arguments by reference. If this test will fail - construct a ReflectionFunction not from name, but from given closureToCall and then return back to the ReflectionFunction->invokeArgs(). Also, update CLAUDE.md file with latest updates (ensure to keep them short and precise) and add a line into the Changelog file to include new feature |
….md and CHANGELOG with FCC changes Agent-Logs-Url: https://github.com/goaop/framework/sessions/cb25cbe1-a51e-454d-a45e-824c14372c49 Co-authored-by: lisachenko <640114+lisachenko@users.noreply.github.com>
Done in 547f377: By-reference test: Added CHANGELOG.md: Added two entries:
CLAUDE.md: Updated the invocation class descriptions to reflect the current FCC-based implementation (was describing the old |
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(). PHP array unpacking preserves reference bindings, so pass-by-reference function arguments work correctly without needingReflectionFunction::invokeArgs.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, includingtestPassByReferenceIsForwardedwhich verifies that by-reference arguments (e.g.\preg_match(...)filling$matches) are correctly propagated back through the invocation chain.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.:Documentation
CHANGELOG.md: added[BC BREAK]entry for the requiredClosure $closureToCallconstructor parameter and a[Feature]entry for first-class callable syntax support.CLAUDE.md: updated invocation class descriptions to reflect the current FCC-based implementation, updated the proxy code example to include the 4th FCC argument, and corrected stale references to the oldClosure::bindconstruction-time approach.