From c259d20562bada94d9bb497e1952d26b96851595 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 14 Mar 2026 14:10:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(php):=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E5=87=BD=E6=95=B0=E8=B0=83=E7=94=A8=E5=8D=A0=E4=BD=8D=E7=AC=A6?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=E5=B9=B6=E5=AE=9E=E7=8E=B0=E4=BD=9C?= =?UTF-8?q?=E7=94=A8=E5=9F=9F=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 AstNodeType 中添加 isPlaceholderExpr 方法用于检测占位符表达式 - 在 CompilerBase 中添加对函数调用占位符的特殊处理逻辑 - 使用 isPlaceholderExpr 替换直接的类型检查以统一占位符判断 - 在 C++ 层面实现 php_switch_scope 和 php_restore_scope 功能 - 在 PlaceHolderGenerator 中添加作用域切换代码生成逻辑 - 添加新的测试用例验证静态方法占位符调用功能 --- src/Php/AstNodeType.php | 6 ++++++ src/Php/CompilerBase.php | 8 ++++++-- src/Php/Generator/PlaceHolderGenerator.php | 5 +++++ src/cpp/main.cc | 19 +++++++++++++++++ src/cpp/php_aot_helper.h | 2 ++ tests/aot/private-prop-002.phpt | 24 ++++++++++++++++++++++ 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/aot/private-prop-002.phpt diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index 1157f976..d7399224 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -11,6 +11,7 @@ namespace PhpAot\Php; use PhpParser\Node; use PhpParser\Node\Expr; use PhpParser\NodeAbstract; +use PhpParser\Node\VariadicPlaceholder; trait AstNodeType { @@ -95,4 +96,9 @@ trait AstNodeType or $expr instanceof Node\Expr\MethodCall or $expr instanceof Node\Expr\StaticCall; } + + protected function isPlaceholderExpr(NodeAbstract $expr): bool + { + return $expr instanceof VariadicPlaceholder; + } } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index aa87c1fd..3c013f32 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -1565,6 +1565,10 @@ class CompilerBase extends \PhpAot\Core\Translator if (!$this->checkAccessible($classDef, $methodDef)) { $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` is not accessible'); } + // 函数调用占位符,不是真实的函数调用 + if (count($expr->args) === 1 and $this->isPlaceholderExpr($expr->args[0])) { + return false; + } if (count($expr->args) < $methodDef->functionDef->argCountRequired) { $this->fatalError($expr, 'Method `' . $classDef->getNamespacedName() . '::' . $method . '()` requires ' . $methodDef->functionDef->argCountRequired . ' arguments, ' . count($expr->args) . ' given'); } elseif (count($expr->args) > count($methodDef->functionDef->argInfoList)) { @@ -2199,7 +2203,7 @@ class CompilerBase extends \PhpAot\Core\Translator $hasNamedArg = false; // 对命名参数进行重排 foreach ($callArgs as $i => $arg) { - if ($arg instanceof Node\VariadicPlaceholder) { + if ($this->isPlaceholderExpr($arg)) { throw new PlaceHolder(); } if ($arg->name) { @@ -2242,7 +2246,7 @@ class CompilerBase extends \PhpAot\Core\Translator $list_args = []; $last = array_key_last($args); foreach ($args as $i => $arg) { - if ($arg instanceof Node\VariadicPlaceholder) { + if ($this->isPlaceholderExpr($arg)) { throw new PlaceHolder(); } if ($arg->name !== null) { diff --git a/src/Php/Generator/PlaceHolderGenerator.php b/src/Php/Generator/PlaceHolderGenerator.php index af609a13..6b2a409b 100644 --- a/src/Php/Generator/PlaceHolderGenerator.php +++ b/src/Php/Generator/PlaceHolderGenerator.php @@ -14,6 +14,11 @@ trait PlaceHolderGenerator { $ce = $this->getClassEntryPtr(\Closure::class); $fn = $ce . ', ' . $this->getFuncPtr('Closure::fromCallable', false); + $tmpVar = $this->genTmpVarName(); + if ($this->classDef) { + $this->beforeStmtLines[] = "auto $tmpVar = php_switch_scope(this_);"; + $this->afterStmtLines[] = "php_restore_scope($tmpVar);"; + } return 'php::call(' . $fn . ', {' . $callable . '})'; } } diff --git a/src/cpp/main.cc b/src/cpp/main.cc index 8d57cca9..0b81b33e 100644 --- a/src/cpp/main.cc +++ b/src/cpp/main.cc @@ -36,6 +36,25 @@ zend_class_entry *php_get_called_ce(php::Object &this_) { } } +static zend_execute_data *get_frame() { + zend_execute_data *frame = EG(current_execute_data); + while (frame && (!frame->func || !ZEND_USER_CODE(frame->func->type))) { + frame = frame->prev_execute_data; + } + return frame; +} + +zend_class_entry *php_switch_scope(php::Object &this_) { + auto frame = get_frame(); + auto ori_scope = frame->func->common.scope; + frame->func->common.scope = php_get_called_ce(this_); + return ori_scope; +} + +void php_restore_scope(zend_class_entry *ori_scope) { + get_frame()->func->common.scope = ori_scope; +} + void module_shutdown(zend_module_entry *module) { /** * There is a bug in PHP's handling of internal strings. All interned strings are released in the request shutdown diff --git a/src/cpp/php_aot_helper.h b/src/cpp/php_aot_helper.h index 287ada20..591378fb 100644 --- a/src/cpp/php_aot_helper.h +++ b/src/cpp/php_aot_helper.h @@ -9,6 +9,8 @@ extern zend_function *php_get_method(int func_id, const php::Str &method_name, i extern const char *php_get_called_class(php::Object &this_); extern zend_class_entry *php_get_called_ce(php::Object &this_); +extern zend_class_entry *php_switch_scope(php::Object &this_); +extern void php_restore_scope(zend_class_entry *ori_scope); static inline php::Variant CALL(int func_id, const php::Str &func_name) { return php::call(php_get_func(func_id, func_name)); diff --git a/tests/aot/private-prop-002.phpt b/tests/aot/private-prop-002.phpt new file mode 100644 index 00000000..bda9f6c4 --- /dev/null +++ b/tests/aot/private-prop-002.phpt @@ -0,0 +1,24 @@ +--TEST-- +Static Class Property Read/Write Test +--FILE-- + +--EXPECT-- +string(19) "Worker::checkErrors" \ No newline at end of file