trait 组合的 protected 方法,同类实例间调用被误判越权:报 cannot access protected method
已提交 issue:(待填)
环境
- TypePHP Compiler (AOT) v0.6.8+(官方 master
10ecb944)
- libphp: PHP 8.4.24 NTS
- OS: WSL2 Ubuntu x86_64
最小复现
<?php
// src/model.php
trait HasEvents {
protected function fire(string $event): string {
return 'fired:' . $event;
}
}
class Base {
use HasEvents;
public function touchOther(): string {
$other = new static();
return $other->fire('x'); // 同类实例间 protected 调用,Zend 合法
}
}
<?php
// src/entry.php
function main(int $argc, array $argv): void {
echo (new Base())->touchOther(), "\n";
}
# typephp.yml
name: protected-scope
build-mode: bin
version: 0.1.0
output: build/protected-scope
build-dir: build/typephp
no-progress: true
sources:
- ./src/entry.php
- ./src/model.php
实际行为(AOT)
PHP Fatal error: Uncaught Error: Invalid callback Base::fire, cannot access protected method Base::fire()
预期行为(Zend)
输出 fired:x。PHP 的可见性是类作用域而非实例作用域:touchOther() 的执行作用域是 Base,对另一个 Base 实例调 protected 方法合法。
关键对照实验
把 fire() 直接声明在类里(不走 trait),同样的跨实例调用在 AOT 下正常:
class Base {
protected function fire(string $event): string { return 'fired:' . $event; }
public function touchOther(): string {
$other = new static();
return $other->fire('x'); // ✅ AOT 下正常
}
}
即问题特异于 trait 组合进来的方法:编译期把 trait 方法注入类时,方法的类作用域注册与直接声明不一致,运行时可见性检查(cannot access protected method)误判越权。
真实影响
命中 Hyperf 的 Model 链:Hyperf\Database\Model\Concerns\HasEvents(trait)定义的 protected fireModelEvent(),在 Model::newFromBuilder() 中以 $model->fireModelEvent('retrieved')(同类另一实例)调用——每次从数据库读取模型都会触发,登录、列表查询等全部崩溃。目前只能把 overlay 副本里的 fireModelEvent 放宽为 public 绕过。
trait 组合的 protected 方法,同类实例间调用被误判越权:报
cannot access protected method环境
10ecb944)最小复现
实际行为(AOT)
预期行为(Zend)
输出
fired:x。PHP 的可见性是类作用域而非实例作用域:touchOther()的执行作用域是Base,对另一个Base实例调 protected 方法合法。关键对照实验
把
fire()直接声明在类里(不走 trait),同样的跨实例调用在 AOT 下正常:即问题特异于 trait 组合进来的方法:编译期把 trait 方法注入类时,方法的类作用域注册与直接声明不一致,运行时可见性检查(
cannot access protected method)误判越权。真实影响
命中 Hyperf 的 Model 链:
Hyperf\Database\Model\Concerns\HasEvents(trait)定义的protected fireModelEvent(),在Model::newFromBuilder()中以$model->fireModelEvent('retrieved')(同类另一实例)调用——每次从数据库读取模型都会触发,登录、列表查询等全部崩溃。目前只能把 overlay 副本里的fireModelEvent放宽为public绕过。