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 $parentClass = '';
protected string $interface = '';
/**
@ -225,6 +226,15 @@ class CompilerBase extends \PhpAot\Core\Translator
protected Parser $parser;
protected PrettyPrinter $printer;
/**
* 在预处理阶段获取所有类的方法名称,检测子类和父类中存在的同名方法,解决动态绑定方法调用的问题
* `static::methodCall()`
* `$this->methodCall()` 子类和父类中存在同名方法
* @var array<string, bool>
*/
protected array $classMethodOverride = [];
protected array $classExtends = [];
public function __construct(string $rootPath)
{
$this->rootPath = $rootPath;
@ -605,6 +615,9 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function getNamespacedClassName(string $class): string
{
if ($class === '') {
$this->error('Class name can not be empty');
}
if ($class[0] === '\\') {
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
{
@ -2105,7 +2119,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$placeHolder = $this->identifierToStr($expr->name);
$fn = $this->getFuncPtr($name);
$this->beforeStmtLines[] = '// Func Call: ' . $name;
$this->beforeStmtLines[] = '// Func Call: ' . $name . '()';
$call = $silent ? 'CALL_SILENT' : 'CALL';
} else {
$tmpVar = $this->genTmpVarName();
@ -3332,9 +3346,14 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$method = $this->identifierToStr($expr->name);
$nativeFunc = $this->findNativeMethod($expr, $object, $method);
if ($nativeFunc) {
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
// 可转为原生调用的 MethodCall
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)) {
@ -3391,6 +3410,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseStaticCall(Node\Expr\StaticCall $expr): string
{
$placeHolder = '';
$self = false;
if ($this->isVarExpr($expr->class) or $this->isVarExpr($expr->name)) {
$var = $this->parseIdentifier($expr->class);
if ($this->isTypedObject($var)) {
@ -3407,6 +3427,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$class = $this->parseIdentifier($expr->class);
if ($class === 'self') {
$class = $this->class;
$self = true;
} elseif ($class === 'parent') {
return $this->parseParentMethodCall($expr);
}
@ -3414,7 +3435,7 @@ class CompilerBase extends \PhpAot\Core\Translator
_do_call:
$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)) {
$nativeFunc = $this->getNativeStaticMethod($class, $method);
@ -3424,10 +3445,15 @@ class CompilerBase extends \PhpAot\Core\Translator
} catch (PlaceHolder) {
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) {
return self::PREFIX . $nativeFunc . '(php::null_object, ' . $args . ')';
return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $args . ')';
} 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
{
$nativeFunc = '';
$classDef = null;
if ($object === 'this_') {
$nativeFunc = $this->getNativeName($method, $this->namespace, $this->class);
$classDef = $this->classDef;
} elseif (isset($this->objects[$object])) {
$class = $this->objects[$object];
if (!$this->hasNativeClass($class)) {
return false;
}
$classDef = $this->getClassDef($class);
if (!$classDef->hasMethod($method)) {
return false;
$methodDef = null;
// 递归查找,若子类中未定义方法,则尝试查找父类是否存在此方法
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)) {
$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);
}
$fullMethodName = $classDef->getNamespacedName(false) . '::' . $method;
// 存在子类同名方法,需要转为动态调用
if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) {
return false;
}
if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) {
return $nativeFunc;
}
@ -3866,6 +3910,9 @@ class CompilerBase extends \PhpAot\Core\Translator
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);
if (empty($expr->args)) {
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
{
$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 = '';
foreach ($class->stmts as $v) {
$type = $v->getType();
@ -181,7 +186,7 @@ class Preprocessor extends CompilerBase
case 'Stmt_EnumCase':
break;
case 'Stmt_ClassMethod':
$code .= $this->prepareFunction($v) . PHP_EOL;
$this->prepareMethod($v);
break;
case 'Stmt_Expression':
$this->foundStrayCode($v);
@ -191,7 +196,27 @@ class Preprocessor extends CompilerBase
}
}
$this->class = '';
$this->parentClass = '';
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