diff --git a/phpunit/code/magic-call-codegen.php b/phpunit/code/magic-call-codegen.php index 959c5a93..c36d4030 100644 --- a/phpunit/code/magic-call-codegen.php +++ b/phpunit/code/magic-call-codegen.php @@ -16,6 +16,10 @@ class RuntimeMagicMethod extends ExactMagicHandler } } +final class ExactInternalMethod extends ArrayObject +{ +} + function exactMagicCall(): mixed { $handler = new ExactMagicHandler(); @@ -27,3 +31,9 @@ function runtimeMagicCall(ExactMagicHandler $handler): mixed return $handler->missing(); } +function exactInternalMethod(): mixed +{ + $object = new ExactInternalMethod(); + $object->append('value'); + return $object->count(); +} diff --git a/phpunit/src/MagicCallCodegenTest.php b/phpunit/src/MagicCallCodegenTest.php index c68b52ef..6c112251 100644 --- a/phpunit/src/MagicCallCodegenTest.php +++ b/phpunit/src/MagicCallCodegenTest.php @@ -15,6 +15,10 @@ final class MagicCallCodegenTest extends \BaseTest $runtimeBody = $this->functionBody($code, 'php_runtimemagiccall'); self::assertStringContainsString('.call(', $runtimeBody); self::assertStringNotContainsString('php_exactmagichandler____call(', $runtimeBody); + + $internalBody = $this->functionBody($code, 'php_exactinternalmethod'); + self::assertStringContainsString('.call(', $internalBody); + self::assertStringNotContainsString('__call(', $internalBody); } private function compileFixture(): string diff --git a/src/Parser/MethodCallTrait.php b/src/Parser/MethodCallTrait.php index 6d9387d5..ecccd0c7 100644 --- a/src/Parser/MethodCallTrait.php +++ b/src/Parser/MethodCallTrait.php @@ -318,23 +318,26 @@ trait MethodCallTrait // otherwise getNativeMethod() would continue into the internal parent // and incorrectly diagnose the absent magic method. $currentClass = $exactClass; - $hasCompiledMagicMethod = false; - while ($this->hasClass($currentClass)) { + while (true) { + if (!$this->hasClass($currentClass)) { + return null; + } $currentDef = $this->getClass($currentClass); if ($currentDef->hasMethod('__call')) { - $hasCompiledMagicMethod = true; break; } - if ($currentDef->extends === '') { - break; + if ($currentDef->extends === '' || !$this->hasClass($currentDef->extends)) { + return null; } $currentClass = $currentDef->extends; } - if (!$hasCompiledMagicMethod) { - return null; - } - $nativeFunc = $this->getNativeMethod($expr, $exactClass, '__call', false); + // Start from the class that actually declares the compiled method. + // This keeps getNativeMethod() out of an internal Zend parent: a real + // inherited internal method also uses DynamicCall, but an internal + // parent without __call must not be diagnosed while probing this + // optimization. + $nativeFunc = $this->getNativeMethod($expr, $currentClass, '__call', false); if ($nativeFunc === false || !$this->hasFunction($nativeFunc)) { return null; }