feat(universal_method): 支持通过反射调用PHP内部函数作为扩展方法

- 移除原有的字符串append方法实现
- 新增universal_method_internal.phpt测试文件验证内部函数扩展方法功能
- 修改findExtensionMethod方法支持先查找用户定义函数再回退到PHP内部函数
- 添加buildInternalExtensionMethod方法构建内部函数扩展方法定义
- 实现validateExtensionFirstParam和validateInternalExtensionFirstParam方法验证参数类型
- 更新测试用例移除已删除的append方法相关测试
pull/1/head
韩天峰 3 months ago
parent 1476e5a4eb
commit 6b5e8ad2e0
  1. 109
      src/Php/UniversalMethodCall.php
  2. 6
      tests/aot/string_method/misc.phpt
  3. 19
      tests/aot/universal_method_internal.phpt

@ -173,7 +173,6 @@ trait UniversalMethodCall
'jsonDecodeToObject' => ['handler' => 'php_fn', 'fn' => 'json_decode', 'return_type' => self::TYPE_OBJECT, 'min_args' => 0, 'max_args' => 2, 'const_args' => [1 => 'false']],
// phpx C++ methods (no PHP function equivalent)
'equals' => ['handler' => 'direct_method', 'method' => 'equals', 'return_type' => self::TYPE_BOOL, 'min_args' => 1, 'max_args' => 2],
'append' => ['handler' => 'direct_method_mutate', 'method' => 'append', 'return_type' => self::TYPE_STR, 'min_args' => 1, 'max_args' => 1],
// conversions
'toInt' => ['handler' => 'convert_fn', 'fn' => 'toInt', 'return_type' => self::TYPE_INT, 'min_args' => 0, 'max_args' => 0],
'toFloat' => ['handler' => 'convert_fn', 'fn' => 'toFloat', 'return_type' => self::TYPE_FLOAT, 'min_args' => 0, 'max_args' => 0],
@ -400,6 +399,9 @@ trait UniversalMethodCall
/**
* Look up an extension function for the given type+method.
* Extension functions follow the naming convention: {typePrefix}_{snake_case_method}
*
* Checks compiled user-defined functions first, then falls back to PHP internal
* functions using reflection to resolve parameter counts and return types.
*/
protected function findExtensionMethod(string $type, string $method): ?array
{
@ -409,25 +411,116 @@ trait UniversalMethodCall
}
$snakeMethod = $this->camelToSnake($method);
$baseName = $prefix . '_' . $snakeMethod;
$funcName = $prefix . '_' . $snakeMethod;
$funcName = $this->resolveExtensionFunctionName($baseName);
if ($funcName === null) {
$resolvedName = $this->resolveExtensionFunctionName($funcName);
if ($resolvedName !== null) {
$funcDef = $this->getFunction($resolvedName);
if (!$this->validateExtensionFirstParam($type, $funcDef)) {
return null;
}
return [
'handler' => 'php_fn',
'fn' => $resolvedName,
'receiver_pos' => 1,
'return_type' => $funcDef->returnType,
'min_args' => 0,
'max_args' => -1,
];
}
if ($this->isInternalFunction($funcName)) {
return $this->buildInternalExtensionMethod($type, $funcName);
}
return null;
}
private function buildInternalExtensionMethod(string $type, string $funcName): ?array
{
$ref = Reflection::getFunction($funcName);
if ($ref === null) {
return null;
}
$funcDef = $this->getFunction($funcName);
$totalParams = $ref->getNumberOfParameters();
if ($totalParams < 1) {
return null;
}
if (!$this->validateInternalExtensionFirstParam($type, $funcName)) {
return null;
}
$phpType = Reflection::getFunctionReturnType($funcName);
$returnType = $phpType ? ($this->zendTypeMap[$phpType] ?? self::TYPE_VAR) : self::TYPE_VAR;
$requiredParams = $ref->getNumberOfRequiredParameters();
$minArgs = max(0, $requiredParams - 1);
$maxArgs = max(0, $totalParams - 1);
if ($totalParams > 0) {
$lastParam = Reflection::getFunctionParameter($funcName, $totalParams - 1);
if ($lastParam !== null && $lastParam->isVariadic()) {
$maxArgs = -1;
}
}
return [
'handler' => 'php_fn',
'fn' => $funcName,
'receiver_pos' => 1,
'return_type' => $funcDef->returnType,
'min_args' => 0,
'max_args' => -1,
'return_type' => $returnType,
'min_args' => $minArgs,
'max_args' => $maxArgs,
];
}
private function validateExtensionFirstParam(string $type, \PhpAot\Php\Entity\FunctionDef $funcDef): bool
{
if (empty($funcDef->argInfoList)) {
return false;
}
$firstParam = $funcDef->argInfoList[0];
// Stream is a PHP pseudo-type; its params are always untyped (TYPE_VAR) or references
if ($type === self::TYPE_STREAM) {
return $firstParam->byRef || $firstParam->type === self::TYPE_VAR || $firstParam->type === self::TYPE_REF;
}
if ($firstParam->byRef) {
return $type === self::TYPE_ARRAY;
}
$paramType = $firstParam->type;
if ($paramType === self::TYPE_VAR) {
return false;
}
return $paramType === $type;
}
private function validateInternalExtensionFirstParam(string $type, string $funcName): bool
{
$param = Reflection::getFunctionParameter($funcName, 0);
if ($param === null) {
return false;
}
// Stream pseudo-type: accept untyped or by-reference first params
if ($type === self::TYPE_STREAM) {
return true;
}
if ($param->isPassedByReference()) {
return $type === self::TYPE_ARRAY;
}
$paramType = $param->getType();
if ($paramType === null) {
return false;
}
if ($paramType instanceof \ReflectionNamedType) {
$phpName = $paramType->getName();
$compilerType = $this->zendTypeMap[$phpName] ?? null;
return $compilerType === $type;
}
return false;
}
protected function resolveExtensionFunctionName(string $funcName): ?string
{
if ($this->hasFunction($funcName)) {

@ -86,11 +86,6 @@ function main()
var_dump($n2->isNumeric());
var_dump($n3->isNumeric());
// append (mutating)
$s = "hello";
$s->append(" world");
var_dump($s);
// substr
$s = "hello world";
var_dump($s->substr(6));
@ -169,7 +164,6 @@ bool(true)
bool(true)
bool(true)
bool(false)
string(11) "hello world"
string(5) "world"
string(5) "hello"
string(5) "world"

@ -0,0 +1,19 @@
--TEST--
Universal method: PHP internal function as extension method via reflection
--FILE--
<?php
use native_types;
function main()
{
$str = "hello";
var_dump($str->rot13());
var_dump($str->rot13()->upper());
var_dump($str->rot13()->length());
}
?>
--EXPECT--
string(5) "uryyb"
string(5) "URYYB"
int(5)
Loading…
Cancel
Save