From 25e567b3db8abd45d21d01adeac79068031ddfa9 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 16 Jan 2026 16:10:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E6=B7=BB=E5=8A=A0=E7=B1=BB?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现了 StaticPropertyFetch 类型检查方法 - 扩展了 ClassDef 构造函数以支持 flags 参数 - 添加了类继承时对 final 类的检查逻辑 - 更新了示例文件 call3.php 中的类结构 - 增加了多个类相关的测试用例文件 - 修改了后置操作解析逻辑以支持静态属性访问 - 在编译器基类中添加了类声明的错误处理 - 实现了对象包装器数组初始化 - 添加了对类可见性构造函数的支持测试 - 更新了接口定义的构造函数实现 --- examples/call3.php | 36 ++++++--- src/Php/AstNodeType.php | 5 ++ src/Php/ClassDef.php | 4 +- src/Php/ClassLikeDef.php | 1 + src/Php/CompilerBase.php | 28 ++++++- src/Php/InterfaceDef.php | 4 + src/Php/Translator.php | 8 +- tests/core/classes/class_example.phpt | 84 +++++++++++++++++++++ tests/core/classes/class_final.phpt | 23 ++++++ tests/core/classes/class_stdclass.phpt | 13 ++++ tests/core/classes/clone_001.phpt | 43 +++++++++++ tests/core/classes/clone_002.phpt | 44 +++++++++++ tests/core/classes/clone_003.phpt | 57 ++++++++++++++ tests/core/classes/clone_004.phpt | 83 ++++++++++++++++++++ tests/core/classes/clone_005.phpt | 24 ++++++ tests/core/classes/clone_006.phpt | 39 ++++++++++ tests/core/classes/ctor_visibility.phpt | 76 +++++++++++++++++++ tests/core/classes/dereferencing_001.phpt | 35 +++++++++ tests/core/classes/destructor_and_echo.phpt | 24 ++++++ 19 files changed, 614 insertions(+), 17 deletions(-) create mode 100644 tests/core/classes/class_example.phpt create mode 100644 tests/core/classes/class_final.phpt create mode 100644 tests/core/classes/class_stdclass.phpt create mode 100644 tests/core/classes/clone_001.phpt create mode 100644 tests/core/classes/clone_002.phpt create mode 100644 tests/core/classes/clone_003.phpt create mode 100644 tests/core/classes/clone_004.phpt create mode 100644 tests/core/classes/clone_005.phpt create mode 100644 tests/core/classes/clone_006.phpt create mode 100644 tests/core/classes/ctor_visibility.phpt create mode 100644 tests/core/classes/dereferencing_001.phpt create mode 100644 tests/core/classes/destructor_and_echo.phpt diff --git a/examples/call3.php b/examples/call3.php index 086431c2..d389ace0 100644 --- a/examples/call3.php +++ b/examples/call3.php @@ -1,21 +1,33 @@ f($a); - $c->f($b); +class Square +{ + function draw() + { + print "Square\n"; + } +} - var_dump($a, $b); +function ShapeFactoryMethod($shape) +{ + switch ($shape) { + case "Circle": + return new Circle(); + case "Square": + return new Square(); + } } +function main() +{ + ShapeFactoryMethod("Circle")->draw(); + ShapeFactoryMethod("Square")->draw(); +} \ No newline at end of file diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index c40ee9d8..48cb266a 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -16,4 +16,9 @@ trait AstNodeType { return $expr instanceof Expr\PropertyFetch; } + + protected function isStaticPropertyFetch(NodeAbstract $expr): bool + { + return $expr instanceof Expr\StaticPropertyFetch; + } } \ No newline at end of file diff --git a/src/Php/ClassDef.php b/src/Php/ClassDef.php index dfed1bbe..8880397b 100644 --- a/src/Php/ClassDef.php +++ b/src/Php/ClassDef.php @@ -18,9 +18,11 @@ class ClassDef extends ClassLikeDef public array $constants = []; public array $implements = []; public string $extends = ''; + public int $flags; - public function __construct(string $name, string $namespace = '') + public function __construct(string $name, int $flags, string $namespace = '') { + $this->flags = $flags; parent::__construct($name, $namespace); } } \ No newline at end of file diff --git a/src/Php/ClassLikeDef.php b/src/Php/ClassLikeDef.php index 9f0fe3f3..3dfebcba 100644 --- a/src/Php/ClassLikeDef.php +++ b/src/Php/ClassLikeDef.php @@ -8,6 +8,7 @@ class ClassLikeDef public string $namespace; public string $extends = ''; + public function __construct(string $name, string $namespace = '') { $this->name = $name; diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 89103e9c..bd150e2d 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -210,6 +210,7 @@ class CompilerBase extends \PhpAot\Core\Translator { $this->localVars = []; $this->arguments = []; + $this->objectWrappers = []; $this->tmpVarIndex = 0; $this->inLoop = false; } @@ -495,6 +496,9 @@ class CompilerBase extends \PhpAot\Core\Translator case 'Stmt_TryCatch': $result = $this->parseTryCatch($v); break; + case 'Stmt_Class': + $this->fatalError($v, 'Cannot declare class in function'); + break; default: abort($v); } @@ -1437,21 +1441,38 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseIdentifier($arg->value); } - protected function parsePostInc($expr): string + protected function parsePostOp($expr, string $op): string { if ($this->isVarExpr($expr->var)) { return $this->parseIdentifier($expr->var) . '++'; } elseif ($this->isPropertyFetch($expr->var)) { $obj = $this->parseIdentifier($expr->var->var); $prop = $this->identifierToStr($expr->var->name); - return $obj . '.setProperty(' . $prop . ', ' . $obj. '.getProperty(' . $prop . ') + 1)'; + $tmpVar = $this->genTmpVarName(); + $this->addLocalVar($tmpVar, self::TYPE_VAR); + $this->beforeStmtLines[] = $tmpVar . ' = ' . $obj. '.getProperty(' . $prop . ');'; + $this->afterStmtLines[] = $obj . '.setProperty(' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);'; + return $tmpVar; + } elseif ($this->isStaticPropertyFetch($expr->var)) { + $class = $this->identifierToStr($expr->var->class); + $prop = $this->identifierToStr($expr->var->name); + $tmpVar = $this->genTmpVarName(); + $this->addLocalVar($tmpVar, self::TYPE_VAR); + $this->beforeStmtLines[] = $tmpVar . ' = ' . 'php::getStaticProperty(' . $class . ', ' . $prop . ');'; + $this->afterStmtLines[] = 'php::setStaticProperty(' . $class . ', ' . $prop . ', ' . $tmpVar . ' ' . $op . ' 1);'; + return $tmpVar; } abort($expr, "Post-increment operator is not supported for non-variable expressions"); } protected function parsePostDec($expr): string { - return $this->parseIdentifier($expr->var) . '--'; + return $this->parsePostOp($expr, '-'); + } + + protected function parsePostInc($expr): string + { + return $this->parsePostOp($expr, '+'); } protected function parseTernary(mixed $expr): string @@ -2254,6 +2275,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected function convertToObject(Node $object): string { $id = $this->parseIdentifier($object); + // TODO 这里的逻辑是不是错误 if ($this->isVarExpr($object) and !$this->hasVar($id)) { $this->addLocalVar($id, self::TYPE_OBJECT); return $id; diff --git a/src/Php/InterfaceDef.php b/src/Php/InterfaceDef.php index ba98d66c..aa6a742b 100644 --- a/src/Php/InterfaceDef.php +++ b/src/Php/InterfaceDef.php @@ -4,4 +4,8 @@ namespace PhpAot\Php; class InterfaceDef extends ClassLikeDef { + public function __construct(string $name, string $namespace = '') + { + parent::__construct($name, $namespace); + } } \ No newline at end of file diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 8aba168d..92049279 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -512,9 +512,15 @@ class Translator extends Preprocessor $this->genClassStubFile($class, $this->file); } - $this->classDef = new ClassDef($this->class, $this->namespace); + $this->classDef = new ClassDef($this->class, $class->flags, $this->namespace); if ($class->extends) { $this->classDef->extends = $this->parseIdentifier($class->extends); + if (isset($this->classes[$this->classDef->extends])) { + $parent = $this->classes[$this->classDef->extends]; + if ($parent->flags & Modifiers::FINAL) { + $this->fatalError($class, "Class `{$this->class}` cannot extend final class `{$this->classDef->extends}`"); + } + } } $this->classDef->implements = $this->parseIdentifierList($class->implements); diff --git a/tests/core/classes/class_example.phpt b/tests/core/classes/class_example.phpt new file mode 100644 index 00000000..8015f253 --- /dev/null +++ b/tests/core/classes/class_example.phpt @@ -0,0 +1,84 @@ +--TEST-- +Classes general test +--FILE-- +first_name."\n"; + echo "Family name:\t ".$this->family_name."\n"; + echo "Address:\t ".$this->address."\n"; + echo "Phone:\t\t ".$this->phone_num."\n"; + echo "\n\n"; + } + function initialize($first_name,$family_name,$address,$phone_num) + { + $this->first_name = $first_name; + $this->family_name = $family_name; + $this->address = $address; + $this->phone_num = $phone_num; + } +}; + +function test($u) +{ /* one can pass classes as arguments */ + $u->display(); + $t = $u; + $t->address = "New address..."; + return $t; /* and also return them as return values */ +} + +function main() { + $user1 = new user; + $user2 = new user; + + $user1->initialize("Zeev","Suraski","Ben Gourion 3, Kiryat Bialik, Israel","+972-4-8713139"); + $user2->initialize("Andi","Gutmans","Haifa, Israel","+972-4-8231621"); + $user1->display(); + $user2->display(); + + $tmp = test($user2); + $tmp->display(); +} +?> +--EXPECT-- +User information +---------------- + +First name: Zeev +Family name: Suraski +Address: Ben Gourion 3, Kiryat Bialik, Israel +Phone: +972-4-8713139 + + +User information +---------------- + +First name: Andi +Family name: Gutmans +Address: Haifa, Israel +Phone: +972-4-8231621 + + +User information +---------------- + +First name: Andi +Family name: Gutmans +Address: Haifa, Israel +Phone: +972-4-8231621 + + +User information +---------------- + +First name: Andi +Family name: Gutmans +Address: New address... +Phone: +972-4-8231621 diff --git a/tests/core/classes/class_final.phpt b/tests/core/classes/class_final.phpt new file mode 100644 index 00000000..51b47e21 --- /dev/null +++ b/tests/core/classes/class_final.phpt @@ -0,0 +1,23 @@ +--TEST-- +ZE2 A final class cannot be inherited +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Class derived cannot extend final class base in %s on line %d diff --git a/tests/core/classes/class_stdclass.phpt b/tests/core/classes/class_stdclass.phpt new file mode 100644 index 00000000..454f2454 --- /dev/null +++ b/tests/core/classes/class_stdclass.phpt @@ -0,0 +1,13 @@ +--TEST-- +Instantiate stdClass +--FILE-- + +--EXPECT-- +stdClass +Done diff --git a/tests/core/classes/clone_001.phpt b/tests/core/classes/clone_001.phpt new file mode 100644 index 00000000..42552f90 --- /dev/null +++ b/tests/core/classes/clone_001.phpt @@ -0,0 +1,43 @@ +--TEST-- +ZE2 object cloning, 1 +--FILE-- +p2 = 'A'; + $obj->p3 = 'B'; + $copy = clone $obj; + $copy->p3 = 'C'; + echo "Object\n"; + var_dump($obj); + echo "Clown\n"; + var_dump($copy); + echo "Done\n"; +} +?> +--EXPECT-- +Object +object(test)#1 (3) { + ["p1"]=> + int(1) + ["p2"]=> + string(1) "A" + ["p3"]=> + string(1) "B" +} +Clown +object(test)#2 (3) { + ["p1"]=> + int(1) + ["p2"]=> + string(1) "A" + ["p3"]=> + string(1) "C" +} +Done diff --git a/tests/core/classes/clone_002.phpt b/tests/core/classes/clone_002.phpt new file mode 100644 index 00000000..59933e04 --- /dev/null +++ b/tests/core/classes/clone_002.phpt @@ -0,0 +1,44 @@ +--TEST-- +ZE2 object cloning, 2 +--FILE-- +p2 = 'A'; + $obj->p3 = 'B'; + $copy = clone $obj; + $copy->p3 = 'C'; + echo "Object\n"; + var_dump($obj); + echo "Clown\n"; + var_dump($copy); + echo "Done\n"; +} +?> +--EXPECT-- +Object +object(test)#1 (3) { + ["p1"]=> + int(1) + ["p2"]=> + string(1) "A" + ["p3"]=> + string(1) "B" +} +Clown +object(test)#2 (3) { + ["p1"]=> + int(1) + ["p2"]=> + string(1) "A" + ["p3"]=> + string(1) "C" +} +Done diff --git a/tests/core/classes/clone_003.phpt b/tests/core/classes/clone_003.phpt new file mode 100644 index 00000000..3c3f6f2e --- /dev/null +++ b/tests/core/classes/clone_003.phpt @@ -0,0 +1,57 @@ +--TEST-- +ZE2 object cloning, 3 +--FILE-- +p5 = 'clone:5'; + } +} +function main() { + $obj = new test; + $obj->p4 = 'A'; + $copy = clone $obj; + echo "Object\n"; + print_r($obj); + echo "Clown\n"; + print_r($copy); + echo "Done\n"; +} +?> +--EXPECT-- +Object +test Object +( + [p1] => test:1 + [p2] => base:2 + [p3] => test:3 + [p4] => A + [p5] => test:5 + [p6:base:private] => base:6 +) +Clown +test Object +( + [p1] => test:1 + [p2] => base:2 + [p3] => test:3 + [p4] => A + [p5] => clone:5 + [p6:base:private] => base:6 +) +Done diff --git a/tests/core/classes/clone_004.phpt b/tests/core/classes/clone_004.phpt new file mode 100644 index 00000000..cdb4581c --- /dev/null +++ b/tests/core/classes/clone_004.phpt @@ -0,0 +1,83 @@ +--TEST-- +ZE2 object cloning, 4 +--FILE-- +a = array(1,2); + $o1->b = array(3,4); + $o1->show(); + + echo "Clone\n"; + $o2 = clone $o1; + $o2->show(); + + echo "Modify\n"; + $o2->a = 5; + $o2->b = 6; + $o2->show(); + + echo "Done\n"; +} +?> +--EXPECT-- +Original +object(test)#1 (2) { + ["a"]=> + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } + ["b"]=> + array(2) { + [0]=> + int(3) + [1]=> + int(4) + } +} +Clone +object(test)#2 (2) { + ["a"]=> + array(2) { + [0]=> + int(1) + [1]=> + int(2) + } + ["b"]=> + array(2) { + [0]=> + int(3) + [1]=> + int(4) + } +} +Modify +object(test)#2 (2) { + ["a"]=> + int(5) + ["b"]=> + int(6) +} +Done diff --git a/tests/core/classes/clone_005.phpt b/tests/core/classes/clone_005.phpt new file mode 100644 index 00000000..4a6d83c4 --- /dev/null +++ b/tests/core/classes/clone_005.phpt @@ -0,0 +1,24 @@ +--TEST-- +ZE2 object cloning, 5 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Cannot override final method base::__clone() in %sclone_005.php on line 11 diff --git a/tests/core/classes/clone_006.phpt b/tests/core/classes/clone_006.phpt new file mode 100644 index 00000000..4ba87b1c --- /dev/null +++ b/tests/core/classes/clone_006.phpt @@ -0,0 +1,39 @@ +--TEST-- +ZE2 object cloning, 6 +--INI-- +error_reporting=2047 +--FILE-- +id = self::$id++; + } + + function __clone() { + $this->address = "New York"; + $this->id = self::$id++; + } +} +function main() { + $original = new MyCloneable(); + + $original->name = "Hello"; + $original->address = "Tel-Aviv"; + + echo $original->id . "\n"; + + $clone = clone $original; + + echo $clone->id . "\n"; + echo $clone->name . "\n"; + echo $clone->address . "\n"; +} +?> +--EXPECTF-- +0 +1 +Hello +New York diff --git a/tests/core/classes/ctor_visibility.phpt b/tests/core/classes/ctor_visibility.phpt new file mode 100644 index 00000000..7ed9d61f --- /dev/null +++ b/tests/core/classes/ctor_visibility.phpt @@ -0,0 +1,76 @@ +--TEST-- +ZE2 A private constructor cannot be called +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Derived::__construct() +Test::__construct() +TestPriv::__construct() +DerivedPriv::__construct() + +Fatal error: Uncaught Error: Cannot call private TestPriv::__construct() in %sctor_visibility.php:%d +Stack trace: +#0 %s(%d): DerivedPriv->__construct() +#1 %s(%d): DerivedPriv::f() +#2 {main} + thrown in %sctor_visibility.php on line %d diff --git a/tests/core/classes/dereferencing_001.phpt b/tests/core/classes/dereferencing_001.phpt new file mode 100644 index 00000000..6170987b --- /dev/null +++ b/tests/core/classes/dereferencing_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +ZE2 dereferencing of objects from methods +--SKIPIF-- + +--FILE-- +name . "\n"; + } +} + +class Person { + private $name; + + function __construct($_name, $_address) { + $this->name = new Name($_name); + } + + function getName() { + return $this->name; + } +} + +function main() { + $person = new Person("John", "New York"); + $person->getName()->display(); +} + +?> +--EXPECT-- +John diff --git a/tests/core/classes/destructor_and_echo.phpt b/tests/core/classes/destructor_and_echo.phpt new file mode 100644 index 00000000..2fa0d6ae --- /dev/null +++ b/tests/core/classes/destructor_and_echo.phpt @@ -0,0 +1,24 @@ +--TEST-- +ZE2 Destructors and echo +--FILE-- + +--EXPECT-- +Test::__construct +Test::__destruct