feat(php): 实现魔术方法 __get、__set、__isset、__unset 的编译支持

- 修改 parseAssignPropertyFetch 方法,添加 literal 参数到 identifierToStr 调用
- 在条件判断中增加对 nativeProperty 属性的检查,区分原生属性和魔术方法调用
- 更新 parseUnset 方法中的属性名解析逻辑,统一使用 identifierToStr 并添加 literal 参数
- 重构 getPropertyIdentifier 方法参数列表,增加 expr 参数并设置 nativeProperty 属性
- 移除类属性不存在时的致命错误抛出,允许魔术方法处理未知属性访问
- 添加完整的魔术方法测试用例,覆盖 __get/__set/__isset/__unset 的基本功能
pull/1/head
韩天峰 5 months ago
parent 45e3933b3c
commit 23f2fa48af
  1. 31
      src/Php/CompilerBase.php
  2. 57
      tests/aot/magic_methods/get_set.phpt

@ -1160,8 +1160,8 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseAssignPropertyFetch(NodeAbstract $left, NodeAbstract $right): string
{
$array = $this->parseIdentifier($left->var);
$propName = $this->identifierToStr($left->name);
$array = $this->parseIdentifier($left->var);
$propName = $this->identifierToStr($left->name, literal: true);
return "{$array}.setProperty({$propName}, " . $this->trimBrackets($this->parseExpr($right)) . ')';
}
@ -1300,7 +1300,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type);
}
} elseif ($this->isPropertyFetch($left)) {
} elseif ($this->isPropertyFetch($left) and !$left->getAttribute('nativeProperty')) {
return $this->parseAssignPropertyFetch($left, $right);
} elseif ($this->isArrayDimFetch($left)) {
$tmp = $this->parseIdentifier($left->var);
@ -2966,20 +2966,20 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function parseUnset(Node\Stmt\Unset_ $node): string
{
$vars = $node->vars;
$vars = $node->vars;
$lines = [];
foreach ($vars as $var) {
$type = $var->getType();
if ($type === self::EXPR_ARRAY_DIM_FETCH) {
$array = $this->parseIdentifier($var->var);
$dim = $this->parseIdentifier($var->dim);
$array = $this->parseIdentifier($var->var);
$dim = $this->parseIdentifier($var->dim);
$lines[] = $array . '.offsetUnset(' . $dim . ');';
} elseif ($type === 'Expr_PropertyFetch') {
$object = $this->parseIdentifier($var->var);
$propName = $this->parseIdentifier($var->name);
$lines[] = $object . '.unsetProperty("' . $propName . '");';
$object = $this->parseIdentifier($var->var);
$propName = $this->identifierToStr($var->name, literal: true);
$lines[] = $object . '.unsetProperty(' . $propName . ');';
} elseif ($type === self::EXPR_VARIABLE) {
$name = $this->parseIdentifier($var);
$name = $this->parseIdentifier($var);
$lines[] = "{$name}.unset();";
} else {
abort($var);
@ -2989,7 +2989,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return implode(PHP_EOL . $this->getIndent(), $lines);
}
protected function getPropertyIdentifier(NodeAbstract $object, NodeAbstract $property): ?string
protected function getPropertyIdentifier(Node\Expr\PropertyFetch $expr, NodeAbstract $object, NodeAbstract $property): ?string
{
if ($this->isVarExpr($object) and $this->isIdExpr($property)) {
$objectName = $this->parseIdentifier($object);
@ -3002,17 +3002,18 @@ class CompilerBase extends \PhpAot\Core\Translator
$nativeProperty = $this->findNativeProperty($object, $propertyName, $className);
}
if ($nativeProperty) {
$expr->setAttribute('nativeProperty', $nativeProperty);
return $nativeProperty;
}
}
return $this->identifierToStr($property);
return $this->identifierToStr($property, literal: true);
}
protected function parsePropertyFetch(Node\Expr\PropertyFetch $expr, bool $update = false): string
{
$object = $expr->var;
$property = $expr->name;
$id = $this->getPropertyIdentifier($object, $property);
$id = $this->getPropertyIdentifier($expr, $object, $property);
return $this->parseIdentifier($object) . '.attr(' . $id . ', ' . $this->escapeBool($update) . ')';
}
@ -3395,7 +3396,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$dim = $this->parseIdentifier($expr->dim);
$list[] = '{php::ArrayDimFetch, ' . self::TYPE_VAR . '(' . $dim . ')}';
} elseif ($this->isPropertyFetch($expr)) {
$name = $this->identifierToStr($expr->name);
$name = $this->identifierToStr($expr->name, literal: true);
$list[] = '{php::PropertyFetch, ' . self::TYPE_VAR . '(' . $name . ')}';
} elseif ($this->isVarExpr($expr)) {
$var = $this->parseIdentifier($expr);
@ -3765,8 +3766,6 @@ class CompilerBase extends \PhpAot\Core\Translator
} elseif ($classDef->extends) {
$findClass = $classDef->extends;
continue;
} else {
$this->fatalError($object, "Property `{$property}` does not exist in class `{$class}`");
}
}
break;

@ -0,0 +1,57 @@
--TEST--
Magic Methods - __get, __set
--SKIPIF--
--FILE--
<?php
class MagicClass {
public array $data = [];
public string $propStr = 'default value';
// __get and __set
public function __get(string $name): mixed {
return $this->data[$name] ?? null;
}
public function __set(string $name, mixed $value): void {
$this->data[$name] = $value;
}
// __isset and __unset
public function __isset(string $name): bool {
return isset($this->data[$name]);
}
public function __unset(string $name): void {
unset($this->data[$name]);
}
}
function main() {
$magic = new MagicClass();
// Test __set and __get
$magic->property = 'test value';
var_dump($magic->property);
$magic->propStr = 'new value';
var_dump($magic->propStr);
$magic->number = 42;
var_dump($magic->number);
// Test __isset and __unset
var_dump(isset($magic->property));
var_dump(isset($magic->nonexistent));
unset($magic->property);
var_dump(isset($magic->property));
}
?>
--EXPECT--
string(10) "test value"
string(9) "new value"
int(42)
bool(true)
bool(false)
bool(false)
Loading…
Cancel
Save