feat(compiler): 实现PHP魔术方法支持和动态调用处理

- 添加__call、__invoke、__toString、__debugInfo等魔术方法测试用例
- 添加__callStatic静态魔术方法支持和测试用例
- 在ClassDef中实现方法名称大小写不敏感的查找机制
- 添加DynamicCall异常用于处理动态方法调用场景
- 改进编译器对__call和__callStatic魔术方法的识别和处理
- 优化方法调用流程以支持动态方法分派机制
- 更新方法注册逻辑使用addMethod方法替代直接赋值操作
pull/1/head
韩天峰 5 months ago
parent a4bc4fcfc0
commit 55caa1fcef
  1. 48
      src/Php/CompilerBase.php
  2. 9
      src/Php/Entity/ClassDef.php
  3. 7
      src/Php/Exception/DynamicCall.php
  4. 2
      src/Php/Translator.php
  5. 16
      tests/aot/magic_methods/call-static.phpt
  6. 52
      tests/aot/magic_methods/call.phpt

@ -15,6 +15,7 @@ use PhpAot\Php\Entity\FunctionDef;
use PhpAot\Php\Entity\InterfaceDef;
use PhpAot\Php\Entity\MethodDef;
use PhpAot\Php\Entity\PropertyDef;
use PhpAot\Php\Exception\DynamicCall;
use PhpAot\Php\Exception\PlaceHolder;
use PhpAot\Php\Exception\Redo;
use PhpAot\Php\Exception\Skip;
@ -1537,6 +1538,11 @@ class CompilerBase extends \PhpAot\Core\Translator
return array_key_exists($this->escapeClass($name), $this->classes);
}
protected function getNativeClass(string $name): ClassDef
{
return $this->classes[$this->escapeClass($name)];
}
/**
* @param string $name 必须传入带有完整命名空间的类名,将会自动转义为 native name
*/
@ -1582,7 +1588,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$classDef = $this->getClassDef($classDef->extends);
} else {
$methodDef = $classDef->methods[$method];
$methodDef = $classDef->getMethod($method);
break;
}
}
@ -3536,19 +3542,24 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = '';
}
$dynamicCall = false;
$method = $this->identifierToStr($expr->name, literal: true);
// 可转为原生调用的 MethodCall
if ($this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) {
$this->context->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()';
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) {
$expr->setAttribute('nativeCall', $nativeFunc);
try {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
} catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$object, $method]));
try {
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) {
$expr->setAttribute('nativeCall', $nativeFunc);
try {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
} catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$object, $method]));
}
}
} catch (DynamicCall) {
$dynamicCall = true;
}
}
@ -3558,7 +3569,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$funcName = '';
}
if ($class and $funcName) {
if ($class and $funcName and !$dynamicCall) {
$methodPtr = $this->getMethodPtr($class, $funcName);
} else {
$methodPtr = $method;
@ -3639,6 +3650,7 @@ class CompilerBase extends \PhpAot\Core\Translator
_do_call:
$method = $this->parseIdentifier($expr->name);
$dynamicCall = false;
$this->context->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()';
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
@ -3647,6 +3659,11 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($callScope) {
$nativeFunc = $this->getNativeMethod($expr, $class, $method);
// 存在 Native 类,但是没有找到方法,可能是动态调用
if (!$nativeFunc and $this->hasNativeClass($class) and $this->getNativeMethod($expr, $class, '__callStatic')) {
$dynamicCall = true;
}
if ($nativeFunc) {
try {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc);
@ -3667,8 +3684,13 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
}
$ce = $this->getClassEntryPtr($class);
$fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method);
if ($dynamicCall) {
$fn = $this->getLiteralString($class . '::' . $method);
} else {
$ce = $this->getClassEntryPtr($class);
$fn = $ce . ', ' . $this->getFuncPtr($class . '::' . $method);
}
$placeHolder = $this->genArray($callScope);
} else {
$fn = 'php::concat({' . $this->identifierToStr($expr->class) . ', "::", ' . $this->identifierToStr($expr->name) . '})';
@ -4075,6 +4097,10 @@ class CompilerBase extends \PhpAot\Core\Translator
} elseif (isset($this->context->objects[$object])) {
$class = $this->context->objects[$object];
$nativeFunc = $this->getNativeMethod($expr, $class, $method);
// 存在 Native 类,但是没有找到方法,可能是动态调用
if (!$nativeFunc and $this->hasNativeClass($class) and $this->getNativeMethod($expr, $class, '__call')) {
throw new DynamicCall;
}
}
if ($classDef) {
$fullMethodName = $classDef->getNamespacedName(false) . '::' . $method;

@ -36,9 +36,14 @@ class ClassDef extends ClassLikeDef
parent::__construct($name, $namespace);
}
public function addMethod(MethodDef $method): void
{
$this->methods[strtolower($method->name)] = $method;
}
public function hasMethod(string $method): bool
{
return isset($this->methods[$method]);
return isset($this->methods[strtolower($method)]);
}
public function hasProperty(string $property): bool
@ -58,7 +63,7 @@ class ClassDef extends ClassLikeDef
public function getMethod($method): MethodDef
{
return $this->methods[$method];
return $this->methods[strtolower($method)];
}
public function getConstant($name): ConstantDef

@ -0,0 +1,7 @@
<?php
namespace PhpAot\Php\Exception;
class DynamicCall extends \RuntimeException
{
}

@ -1064,7 +1064,7 @@ class Translator extends Preprocessor
$methodCodes[$name] = $this->parseFunction($v);
$this->checkRequiredArgNum($name, $this->methodDef, $v);
$this->classDef->methods[$name] = $this->methodDef;
$this->classDef->addMethod($this->methodDef);
}
$this->resetMethod();

@ -0,0 +1,16 @@
--TEST--
Magic Methods - __get, __set, __call, __invoke etc.
--FILE--
<?php
class MagicClass {
public static function __CallStatic(string $name, array $arguments): mixed {
return "Called static method: {$name} with args: " . implode(', ', $arguments);
}
}
function main() {
var_dump(MagicClass::staticMethod('static1', 'static2'));
}
?>
--EXPECT--
string(62) "Called static method: staticMethod with args: static1, static2"

@ -0,0 +1,52 @@
--TEST--
Magic Methods - __get, __set, __call, __invoke etc.
--FILE--
<?php
class MagicClass {
// __call for instance methods
public function __call(string $name, array $arguments): mixed {
return "Called method: {$name} with args: " . implode(', ', $arguments);
}
// __invoke
public function __invoke(mixed $value): mixed {
return "Invoked with: {$value}";
}
// __toString
public function __toString(): string {
return "MagicClass instance";
}
// __debugInfo
public function __debugInfo(): array {
return ['custom' => 'debug info', 'data' => 'hello world'];
}
}
function main() {
$magic = new MagicClass();
// Test __call
var_dump($magic->someMethod('arg1', 'arg2'));
// Test __invoke
var_dump($magic('hello'));
// Test __toString
echo $magic . "\n";
// Test __debugInfo
var_dump($magic);
}
?>
--EXPECT--
string(47) "Called method: someMethod with args: arg1, arg2"
string(19) "Invoked with: hello"
MagicClass instance
object(MagicClass)#1 (2) {
["custom"]=>
string(10) "debug info"
["data"]=>
string(11) "hello world"
}
Loading…
Cancel
Save