feat(compiler): 添加类方法重写支持和动态绑定机制

- 新增 parentClass 属性用于存储父类名称
- 实现 classMethodOverride 和 classExtends 数组用于检测方法重写
- 添加方法重写预处理逻辑,支持子类和父类同名方法的动态绑定
- 修复静态方法调用时的对象指针传递问题
- 增强方法查找机制,支持向上递归查找父类方法
- 添加测试用例验证方法重写功能的正确性
- 改进错误提示信息,提供更准确的错误定位
pull/1/head
韩天峰 6 months ago
parent 1ce204d4b2
commit 7871eb2820
  1. 69
      src/Php/CompilerBase.php
  2. 27
      src/Php/Preprocessor.php
  3. 44
      tests/aot/class-method-override.phpt

@ -147,6 +147,7 @@ class CompilerBase extends \PhpAot\Core\Translator
* 原始类名,不包含命名空间. * 原始类名,不包含命名空间.
*/ */
protected string $class = ''; protected string $class = '';
protected string $parentClass = '';
protected string $interface = ''; protected string $interface = '';
/** /**
@ -225,6 +226,15 @@ class CompilerBase extends \PhpAot\Core\Translator
protected Parser $parser; protected Parser $parser;
protected PrettyPrinter $printer; protected PrettyPrinter $printer;
/**
* 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题
* `static::methodCall()`
* `$this->methodCall()` 子类和父类中存在同名方法
* @var array<string, bool>
*/
protected array $classMethodOverride = [];
protected array $classExtends = [];
public function __construct(string $rootPath) public function __construct(string $rootPath)
{ {
$this->rootPath = $rootPath; $this->rootPath = $rootPath;
@ -605,6 +615,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function getNamespacedClassName(string $class): string protected function getNamespacedClassName(string $class): string
{ {
if ($class === '') {
$this->error('Class name can not be empty');
}
if ($class[0] === '\\') { if ($class[0] === '\\') {
return ltrim($class, '\\'); return ltrim($class, '\\');
} }
@ -2025,7 +2038,8 @@ class CompilerBase extends \PhpAot\Core\Translator
/** /**
* 查找原生函数. * 查找原生函数.
* *
* @return bool * @param string $fname
* @return string|false
*/ */
protected function findNativeFunction(string $fname): string|false protected function findNativeFunction(string $fname): string|false
{ {
@ -2105,7 +2119,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$placeHolder = $this->identifierToStr($expr->name); $placeHolder = $this->identifierToStr($expr->name);
$fn = $this->getFuncPtr($name); $fn = $this->getFuncPtr($name);
$this->beforeStmtLines[] = '// Func Call: ' . $name; $this->beforeStmtLines[] = '// Func Call: ' . $name . '()';
$call = $silent ? 'CALL_SILENT' : 'CALL'; $call = $silent ? 'CALL_SILENT' : 'CALL';
} else { } else {
$tmpVar = $this->genTmpVarName(); $tmpVar = $this->genTmpVarName();
@ -3332,9 +3346,14 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$method = $this->identifierToStr($expr->name); $method = $this->identifierToStr($expr->name);
$nativeFunc = $this->findNativeMethod($expr, $object, $method);
if ($nativeFunc) { // 可转为原生调用的 MethodCall
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); if ($this->isVarExpr($expr->var) and $this->isNamedMethod($expr->name)) {
$this->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()';
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
}
} }
if ($this->isNamedMethod($expr->name)) { if ($this->isNamedMethod($expr->name)) {
@ -3391,6 +3410,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseStaticCall(Node\Expr\StaticCall $expr): string protected function parseStaticCall(Node\Expr\StaticCall $expr): string
{ {
$placeHolder = ''; $placeHolder = '';
$self = false;
if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) { if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) {
$var = $this->parseIdentifier($expr->class); $var = $this->parseIdentifier($expr->class);
if ($this->isTypedObject($var)) { if ($this->isTypedObject($var)) {
@ -3407,6 +3427,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = $this->parseIdentifier($expr->class); $class = $this->parseIdentifier($expr->class);
if ($class === 'self') { if ($class === 'self') {
$class = $this->class; $class = $this->class;
$self = true;
} elseif ($class === 'parent') { } elseif ($class === 'parent') {
return $this->parseParentMethodCall($expr); return $this->parseParentMethodCall($expr);
} }
@ -3414,7 +3435,7 @@ class CompilerBase extends \PhpAot\Core\Translator
_do_call: _do_call:
$method = $this->parseIdentifier($expr->name); $method = $this->parseIdentifier($expr->name);
$this->beforeStmtLines[] = '// Static Call: ' . $class . '::' . $method; $this->beforeStmtLines[] = '// Static Method Call: ' . $class . '::' . $method . '()';
if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) { if ($this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$nativeFunc = $this->getNativeStaticMethod($class, $method); $nativeFunc = $this->getNativeStaticMethod($class, $method);
@ -3424,10 +3445,15 @@ class CompilerBase extends \PhpAot\Core\Translator
} catch (PlaceHolder) { } catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)])); return $this->genPlaceHolder($this->genArray([$this->genCharPtr($class, true), $this->genCharPtr($method)]));
} }
$object = 'php::null_object';
// 在方法定义中使用了当前类的方法 self::method(),依然应该传递 this_ 指针
if ($this->methodDef and $self) {
$object = 'this_';
}
if ($args) { if ($args) {
return self::PREFIX . $nativeFunc . '(php::null_object, ' . $args . ')'; return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')';
} else { } else {
return self::PREFIX . $nativeFunc . '(php::null_object)'; return self::PREFIX . $nativeFunc . '(' . $object . ')';
} }
} }
} }
@ -3814,18 +3840,31 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function findNativeMethod(Node\Expr\MethodCall $expr, string $object, string $method): string|false protected function findNativeMethod(Node\Expr\MethodCall $expr, string $object, string $method): string|false
{ {
$nativeFunc = ''; $nativeFunc = '';
$classDef = null;
if ($object === 'this_') { if ($object === 'this_') {
$nativeFunc = $this->getNativeName($method, $this->namespace, $this->class); $nativeFunc = $this->getNativeName($method, $this->namespace, $this->class);
$classDef = $this->classDef;
} elseif (isset($this->objects[$object])) { } elseif (isset($this->objects[$object])) {
$class = $this->objects[$object]; $class = $this->objects[$object];
if (!$this->hasNativeClass($class)) { if (!$this->hasNativeClass($class)) {
return false; return false;
} }
$classDef = $this->getClassDef($class); $classDef = $this->getClassDef($class);
if (!$classDef->hasMethod($method)) { $methodDef = null;
return false; // 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法
while ($classDef) {
if (!$classDef->hasMethod($method)) {
if (!$classDef->extends) {
return false;
}
$classDef = $this->getClassDef($classDef->extends);
} else {
$methodDef = $classDef->methods[$method];
break;
}
} }
$methodDef = $classDef->methods[$method];
if (!$this->checkAccessible($classDef, $methodDef)) { if (!$this->checkAccessible($classDef, $methodDef)) {
$this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible');
} }
@ -3836,6 +3875,11 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name); $nativeFunc = $this->getNativeName($method, $classDef->namespace, $classDef->name);
} }
$fullMethodName = $classDef->getNamespacedName(false) . '::' . $method;
// 存在子类同名方法,需要转为动态调用
if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) {
return false;
}
if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) { if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) {
return $nativeFunc; return $nativeFunc;
} }
@ -3866,6 +3910,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseParentMethodCall(Node\Expr\StaticCall $expr): string protected function parseParentMethodCall(Node\Expr\StaticCall $expr): string
{ {
if (!$this->classDef->extends) {
$this->fatalError($expr, 'Cannot call parent method `' . $this->classDef->name . '::' . $this->parseIdentifier($expr->name) . '()` because class `' . $this->classDef->name . '` does not extend any class');
}
$method = $this->identifierToStr($expr->name); $method = $this->identifierToStr($expr->name);
if (empty($expr->args)) { if (empty($expr->args)) {
return 'this_.callParentMethod(' . $method . ')'; return 'this_.callParentMethod(' . $method . ')';

@ -170,6 +170,11 @@ class Preprocessor extends CompilerBase
protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string protected function prepareClass(Node\Stmt\Class_|Node\Stmt\Trait_|Node\Stmt\Enum_ $class): string
{ {
$this->class = $this->parseIdentifier($class->name); $this->class = $this->parseIdentifier($class->name);
if ($class->extends) {
$this->parentClass = $this->getNamespacedClassName($this->parseIdentifier($class->extends));
$fullClassName = $this->getNamespacedClassName($this->class);
$this->classExtends[$fullClassName] = $this->parentClass;
}
$code = ''; $code = '';
foreach ($class->stmts as $v) { foreach ($class->stmts as $v) {
$type = $v->getType(); $type = $v->getType();
@ -181,7 +186,7 @@ class Preprocessor extends CompilerBase
case 'Stmt_EnumCase': case 'Stmt_EnumCase':
break; break;
case 'Stmt_ClassMethod': case 'Stmt_ClassMethod':
$code .= $this->prepareFunction($v) . PHP_EOL; $this->prepareMethod($v);
break; break;
case 'Stmt_Expression': case 'Stmt_Expression':
$this->foundStrayCode($v); $this->foundStrayCode($v);
@ -191,7 +196,27 @@ class Preprocessor extends CompilerBase
} }
} }
$this->class = ''; $this->class = '';
$this->parentClass = '';
return $code; return $code;
} }
protected function prepareMethod(Node\Stmt\ClassMethod $v): void
{
$this->prepareFunction($v) . PHP_EOL;
if ($this->isIdExpr($v->name) or $this->isNameExpr($v->name)) {
$fullClassName = $this->getNamespacedClassName($this->class);
$fullName = $fullClassName . '::' . $v->name;
$this->classMethodOverride[$fullName] = false;
// 查找父类是否有同名方法,递归查找
while (isset($this->classExtends[$fullClassName])) {
$parentClass = $this->classExtends[$fullClassName];
$parentMethod = $parentClass . '::' . $v->name;
if (isset($this->classMethodOverride[$parentMethod])) {
$this->classMethodOverride[$parentMethod] = true;
}
$fullClassName = $parentClass;
}
}
}
} }

@ -0,0 +1,44 @@
--TEST--
class method override
--FILE--
<?php
namespace Test {
class Worker1
{
protected string $id = 'foo';
function hello()
{
var_dump($this->id);
return "hello";
}
function foo()
{
var_dump($this->hello());
var_dump(self::hello());
}
}
class Worker2 extends Worker1
{
function hello()
{
return "world";
}
}
}
namespace {
use Test\Worker2;
function main()
{
$obj = new Worker2();
$obj->hello();
$obj->foo();
}
}
?>
--EXPECT--
string(5) "world"
string(3) "foo"
string(5) "hello"
Loading…
Cancel
Save