refactor(php): 重构属性访问解析逻辑以使用专门的解析器方法

- 将 findNativeProperty 方法替换为更具体的 resolveNativeInstanceProperty 和 resolveNativeStaticProperty
- 添加 resolveNullsafePropertyChain 方法处理空安全属性链检查
- 引入 PropertyAccessResult 类来统一属性访问结果的返回格式
- 使用 isNativePropertyAccess 方法替代直接的属性检查
- 移除旧的泛型 findNativeProperty 入口点以避免布尔参数扩散
- 更新 AssignOpTrait 中的属性访问检查逻辑
- 修改动态属性递增递减操作中的原生属性检查方式
- 调整编译器基类中的属性解析实现细节
pull/5/head
韩天峰 2 months ago
parent 4b1b20c7ca
commit 87c54236a0
  1. 5
      docs/REFACTORING_PLAN.md
  2. 99
      src/Php/CompilerBase.php
  3. 4
      src/Php/Parser/AssignOpTrait.php
  4. 51
      src/Php/Resolver/PropertyAccessResolver.php

@ -234,7 +234,10 @@
当前进展:
- 已建立 `PropertyAccessResolver``PropertyAccessResult` 作为属性访问解析的第一层抽象。
- `CompilerBase::findNativeProperty()` 保留为兼容入口,并委托 resolver 执行 native property 查找、static-vs-instance 检查和可见性检查。
- 实例属性读取已通过 resolver 的 `resolveNativeInstanceProperty()` 显式接口完成 native property 查找、static-vs-instance 检查和可见性检查。
- `findNativeStaticProperty()` 已通过 resolver 的 `resolveNativeStaticProperty()` 显式接口完成静态属性检查。
- nullsafe 属性链检查已通过 resolver 的 `resolveNullsafePropertyChain()` 显式接口完成类名推进和可见性检查。
- 旧的 `CompilerBase::findNativeProperty()` 泛型入口已移除,避免后续继续扩散带 `$static` 布尔参数的访问模式。
- `CompilerBase::isSameClassName()`、`isSameOrSubclassOf()`、`canAccessProtectedProperty()` 已委托 resolver,避免规则继续扩散。
- 已添加 `prepare/convert/idle` 编译阶段状态;`PropertyAccessResolver` 只能在 convert 阶段创建和使用,避免预处理阶段误用不完整的类表状态。
- 当前迁移保持生成代码不变,后续阶段再统一 read/write emitter。

@ -43,6 +43,7 @@ use PhpAot\Php\Platform\Macos;
use PhpAot\Php\Platform\PlatformBase;
use PhpAot\Php\Platform\PlatformFactory;
use PhpAot\Php\Platform\Windows;
use PhpAot\Php\Resolver\PropertyAccessResult;
use PhpAot\Php\Resolver\PropertyAccessResolver;
use PhpParser\Modifiers;
use PhpParser\Node;
@ -2771,7 +2772,7 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected function genDynamicPropIncDec($var, string $op, bool $isPre): ?string
{
if (!$this->isPropertyFetch($var) || $var->getAttribute('nativeProperty')) {
if (!$this->isPropertyFetch($var) || $this->isNativePropertyAccess($var)) {
return null;
}
@ -4893,13 +4894,14 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($this->classDef->trait) {
goto _dynamic_attr;
}
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $this->getFullClassName());
$result = $this->resolveNativeInstanceProperty($expr, $propertyName, $this->getFullClassName());
$nativeProperty = $result ? $this->applyNativePropertyAccessResult($expr, $result) : null;
} elseif ($this->isTypedObject($objectName)) {
$className = $this->getObjectType($objectName);
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $className);
$result = $this->resolveNativeInstanceProperty($expr, $propertyName, $className);
$nativeProperty = $result ? $this->applyNativePropertyAccessResult($expr, $result) : null;
}
if ($nativeProperty) {
$expr->setAttribute('nativeProperty', $nativeProperty);
return $nativeProperty;
}
}
@ -5427,7 +5429,7 @@ class CompilerBase extends \PhpAot\Core\Translator
// 单属性读取(非链式)
if ($this->isPropertyFetch($expr) and $this->isVarExpr($expr->var) and $this->isIdExpr($expr->name)) {
$prop = $this->parsePropertyFetch($expr);
if ($expr->hasAttribute('nativeProperty')) {
if ($this->isNativePropertyAccess($expr)) {
if ($op === self::OP_REFVAL) {
return $prop . '.toReference()';
}
@ -5436,7 +5438,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
if ($this->isStaticPropertyFetch($expr) and $this->isNameExpr($expr->class) and $this->isIdExpr($expr->name)) {
$prop = $this->parseStaticPropertyFetch($expr);
if ($expr->hasAttribute('nativeProperty')) {
if ($this->isNativePropertyAccess($expr)) {
if ($op === self::OP_REFVAL) {
return $prop . '.toReference()';
}
@ -5825,10 +5827,9 @@ class CompilerBase extends \PhpAot\Core\Translator
} else {
$class = $this->getNamespacedClassName($class);
}
$nativeProperty = $this->findNativeProperty($expr, $propertyName, $class, true);
if ($nativeProperty) {
$expr->setAttribute('nativeProperty', $nativeProperty);
return $nativeProperty;
$result = $this->resolveNativeStaticProperty($expr, $propertyName, $class);
if ($result !== null) {
return $this->applyNativePropertyAccessResult($expr, $result);
}
}
return null;
@ -5859,20 +5860,30 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->createPropertyAccessResolver()->canAccessProtectedProperty($scope, $declaringClass);
}
/**
* @param NodeAbstract $expr 仅用于输出错误日志
* @param string $class 必须传入带有完整命名空间的类名
*/
protected function findNativeProperty(NodeAbstract $expr, string $property, string $class, bool $static = false): ?string
private function resolveNativeInstanceProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult
{
$scope = $this->class ? $this->getFullClassName() : '';
$result = $this->createPropertyAccessResolver()->resolveNativeProperty($expr, $property, $class, $scope, $static);
if ($result !== null) {
$expr->setAttribute('nativePropertyDef', $result->propertyDef);
$expr->setAttribute('nativeClassDef', $result->classDef);
return $this->getPropertyOffset($result->classDef->getNamespacedName(false), $result->property);
}
return null;
return $this->createPropertyAccessResolver()->resolveNativeInstanceProperty($expr, $property, $class, $scope);
}
private function resolveNativeStaticProperty(NodeAbstract $expr, string $property, string $class): ?PropertyAccessResult
{
$scope = $this->class ? $this->getFullClassName() : '';
return $this->createPropertyAccessResolver()->resolveNativeStaticProperty($expr, $property, $class, $scope);
}
private function applyNativePropertyAccessResult(NodeAbstract $expr, PropertyAccessResult $result): string
{
$offset = $this->getPropertyOffset($result->classDef->getNamespacedName(false), $result->property);
$expr->setAttribute('nativePropertyDef', $result->propertyDef);
$expr->setAttribute('nativeClassDef', $result->classDef);
$expr->setAttribute('nativeProperty', $offset);
return $offset;
}
protected function isNativePropertyAccess(NodeAbstract $expr): bool
{
return $expr->hasAttribute('nativeProperty');
}
protected function parseNativeStaticPropertyFetch(Expr\StaticPropertyFetch $expr): string|bool
@ -5913,7 +5924,7 @@ class CompilerBase extends \PhpAot\Core\Translator
return $refVar;
}
if ($expr->hasAttribute('nativeProperty')) {
if ($this->isNativePropertyAccess($expr)) {
$classPtr = $this->getClassEntryPtr($class);
return Symbol::getStaticProperty() . '(' . $classPtr . ', ' . $nativeProp . ')';
} else {
@ -6895,37 +6906,37 @@ class CompilerBase extends \PhpAot\Core\Translator
private function checkNullsafePropertyAccesses(NodeAbstract $baseExpr, array $list): void
{
$className = $this->detectClassOfExpr($baseExpr);
if ($className === '') {
return;
}
$properties = [];
foreach ($list as $item) {
if ($item[0] !== 'property') {
$className = '';
continue;
break;
}
/** @var Expr\NullsafePropertyFetch $node */
$node = $item[2];
if (!$this->isIdExpr($node->name)) {
$className = '';
continue;
break;
}
$property = $this->parseIdentifier($node->name);
$this->findNativeProperty($node, $property, $className);
if (!$node->hasAttribute('nativePropertyDef')) {
$className = '';
continue;
}
$properties[] = [
'node' => $node,
'property' => $this->parseIdentifier($node->name),
];
}
/** @var PropertyDef $def */
$def = $node->getAttribute('nativePropertyDef');
$className = $def->type === self::TYPE_OBJECT ? $def->class : '';
if ($className === '') {
return;
}
if (!$properties) {
return;
}
$scope = $this->class ? $this->getFullClassName() : '';
$results = $this->createPropertyAccessResolver()->resolveNullsafePropertyChain(
$this->detectClassOfExpr($baseExpr),
$properties,
$scope,
self::TYPE_OBJECT,
);
foreach ($results as $index => $result) {
$this->applyNativePropertyAccessResult($properties[$index]['node'], $result);
}
}

@ -260,7 +260,7 @@ trait AssignOpTrait
$this->checkVarAssignExpr($left, $finalVarType, $type);
}
}
} elseif ($this->isPropertyFetch($left) and !$left->getAttribute('nativeProperty')) {
} elseif ($this->isPropertyFetch($left) and !$this->isNativePropertyAccess($left)) {
return $this->parseAssignPropertyFetch($left, $right);
} elseif ($this->isArrayDimFetch($left) and $this->isVarExpr($left->var)) {
$tmp = $this->parseIdentifier($left->var);
@ -397,7 +397,7 @@ trait AssignOpTrait
return $this->parseArrayDimStore($node->var->var, $dim, $tmpVar);
}
if ($this->isPropertyFetch($node->var) and !$node->var->getAttribute('nativeProperty')) {
if ($this->isPropertyFetch($node->var) and !$this->isNativePropertyAccess($node->var)) {
$obj = $this->parseIdentifier($node->var->var);
$propName = $this->identifierToStr($node->var->name, literal: true);
$binaryOp = $this->removeAssignOp($op);

@ -102,6 +102,57 @@ final class PropertyAccessResolver
return null;
}
public function resolveNativeInstanceProperty(
NodeAbstract $expr,
string $property,
string $class,
string $scope,
): ?PropertyAccessResult {
return $this->resolveNativeProperty($expr, $property, $class, $scope);
}
public function resolveNativeStaticProperty(
NodeAbstract $expr,
string $property,
string $class,
string $scope,
): ?PropertyAccessResult {
return $this->resolveNativeProperty($expr, $property, $class, $scope, true);
}
/**
* @param array<int, array{node: NodeAbstract, property: string}> $properties
* @return array<int, PropertyAccessResult>
*/
public function resolveNullsafePropertyChain(
string $baseClass,
array $properties,
string $scope,
string $objectType,
): array {
if ($baseClass === '') {
return [];
}
$className = $baseClass;
$resolved = [];
foreach ($properties as $index => $property) {
$result = $this->resolveNativeProperty($property['node'], $property['property'], $className, $scope);
if ($result === null) {
break;
}
$resolved[$index] = $result;
$def = $result->propertyDef;
if ($def->type !== $objectType || $def->class === '') {
break;
}
$className = $def->class;
}
return $resolved;
}
private function fatal(NodeAbstract $expr, string $message): never
{
($this->fatalError)($expr, $message);

Loading…
Cancel
Save