feat(php): 添加对内部类继承和可空参数的支持

- 在 ArgInfo 类中添加 nullable 属性用于标识参数是否可空
- 在 ClassDef 类中添加 inheritedFromInternalClass 属性用于标记是否继承自内部类
- 实现对继承自内部类的动态方法调用支持
- 添加 Reflection::isInternalClass 方法用于判断类是否为内部类
- 添加 Reflection::hasMethod 方法用于检查类是否存在指定方法
- 在参数解析时设置 nullable 标志
- 修改编译器基类以支持内部类继承的错误处理
- 更新翻译器以正确处理可空参数的调用逻辑
pull/1/head
韩天峰 5 months ago
parent 86055c3bf1
commit 096a92a62e
  1. 40
      examples/extends/test.php
  2. 1
      src/Php/ArgInfo.php
  3. 14
      src/Php/CompilerBase.php
  4. 1
      src/Php/Entity/ClassDef.php
  5. 32
      src/Php/Reflection.php
  6. 23
      src/Php/Translator.php
  7. 24
      tests/aot/extends-redis.phpt

@ -0,0 +1,40 @@
<?php
class A
{
public function __construct()
{
echo "A::__construct()\n";
}
}
class B extends A
{
public function __construct()
{
parent::__construct();
echo "B::__construct()\n";
}
public function foo()
{
var_dump(__METHOD__);
}
}
class C extends ArrayObject {
}
function main()
{
$o = new B;
$o->foo();
$c = new C;
$c->offsetSet(0, 1);
$c->offsetSet(1, 2);
var_dump($c);
var_dump($c->foo());
}

@ -18,5 +18,6 @@ class ArgInfo
public ?Expr $defaultValue = null;
public bool $byRef = false;
public bool $variadic = false;
public bool $nullable = false;
public bool $property = false;
}

@ -215,6 +215,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected ?MethodDef $methodDef = null;
protected ?InterfaceDef $interfaceDef = null;
protected FunctionContext $context;
protected array $superGlobalVars = [
'_GET' => self::TYPE_ARRAY,
'_POST' => self::TYPE_ARRAY,
@ -1014,6 +1015,9 @@ class CompilerBase extends \PhpAot\Core\Translator
$argInfo->byRef = $param->byRef;
$argInfo->variadic = $param->variadic;
$argInfo->property = $param->isPromoted();
if ($param->type and $param->type instanceof Node\NullableType) {
$argInfo->nullable = true;
}
if ($param->default) {
$defaultValueCount++;
$argInfo->default = $this->parseParamDefaultValue($param->default);
@ -1606,7 +1610,15 @@ class CompilerBase extends \PhpAot\Core\Translator
return false;
}
if (!$this->hasNativeClass($classDef->extends)) {
$this->climate->error('Native method `' . $class . '::' . $method . '()` not found, the parent class `' . $classDef->extends . '` is not defined');
if ($classDef->inheritedFromInternalClass) {
if (!Reflection::hasMethod($classDef->extends, $method) and !Reflection::hasMethod($classDef->extends, $method . '__call')) {
$this->fatalError($expr, 'Class `' . $classDef->getNamespacedName() . '` inherits from a internal class, but the class `' .
$classDef->extends . '` does not have a `' . $method . '` method or a `__call` magic method');
} else {
$this->climate->cyan('Dynamically calling internal class method `' . $classDef->extends . '::' . $method . '()`');
throw new DynamicCall;
}
}
return false;
}
$classDef = $this->getClassDef($classDef->extends);

@ -29,6 +29,7 @@ class ClassDef extends ClassLikeDef
public bool $requireCtor = false;
public bool $enum = false;
public int $flags;
public bool $inheritedFromInternalClass = false;
public function __construct(string $name, int $flags, string $namespace = '')
{

@ -13,6 +13,29 @@ class Reflection
private static array $functions = [];
private static array $classes = [];
public static function isInternalClass(string $class): bool
{
static $internalClasses = null;
if ($internalClasses === null) {
$allClasses = get_declared_classes();
$internalClasses = [];
foreach ($allClasses as $className) {
try {
$ref = new \ReflectionClass($className);
if ($ref->isInternal()) {
$internalClasses[strtolower($className)] = true;
}
} catch (\ReflectionException) {
continue;
}
}
}
return isset($internalClasses[strtolower($class)]);
}
public static function getFunction(string $fn): ?\ReflectionFunction
{
if (!isset(self::$functions[$fn])) {
@ -104,4 +127,13 @@ class Reflection
}
return $param->isPassedByReference() ? $param->getName() : null;
}
public static function hasMethod(string $extends, string $method): bool
{
$class = self::getClass($extends);
if (!$class) {
return false;
}
return $class->hasMethod($method);
}
}

@ -241,6 +241,10 @@ class Translator extends Preprocessor
if (ctype_digit($this->targetName[0])) {
return 'app_' . $this->targetName;
}
$extensions = get_loaded_extensions();
if (in_array($this->targetName, $extensions)) {
return $this->targetName . '_';
}
return $this->targetName;
}
@ -812,11 +816,20 @@ class Translator extends Preprocessor
}
if ($extends) {
$this->classDef->extends = $this->getParentClass($class->extends);
if (isset($this->classes[$this->classDef->extends])) {
$parent = $this->classes[$this->classDef->extends];
$parentClass = $this->getParentClass($class->extends);
if ($this->hasNativeClass($parentClass)) {
$parent = $this->getClassDef($parentClass);
if ($parent->flags & Modifiers::FINAL) {
$this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$this->classDef->extends}`");
$this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$parentClass}`");
}
$this->classDef->extends = $parentClass;
$this->classDef->inheritedFromInternalClass = false;
} else {
if (Reflection::isInternalClass($parentClass)) {
$this->classDef->extends = $parentClass;
$this->classDef->inheritedFromInternalClass = true;
} else {
$this->fatalError($class, "Class `{$this->class}` inherits from a non-existent class `$parentClass`");
}
}
}
@ -886,6 +899,8 @@ class Translator extends Preprocessor
} else {
if ($argInfo->byRef) {
$argExpr = 'php::getCallArgByRef(' . $k . ')';
} elseif ($argInfo->nullable) {
$argExpr = 'php::getCallArg(' . $k . ', php::null)';
} else {
$argExpr = 'php::getCallArg(' . $k . ')';
}

@ -0,0 +1,24 @@
--TEST--
extends redis
--FILE--
<?php
class MyRedis extends \redis
{
public function __construct(?array $options = null)
{
parent::__construct($options);
}
}
function main()
{
$o = new MyRedis;
$o->connect('127.0.0.1', 6379);
$uuid = uniqid();
var_dump($o->set('key', $uuid));
var_dump($o->get('key') === $uuid);
}
?>
--EXPECT--
bool(true)
bool(true)
Loading…
Cancel
Save