PHP Reflection异常:getType()->getName()返回无关值,是Bug还是预期行为?
哇,这个问题确实够诡异的——反射类型名称居然被属性值里的字符串给覆盖了,还跟字符串长度、var_export扯上关系,换谁都会怀疑是PHP Bug。我来帮你拆解一下:
首先,这确实是PHP核心反射模块的一个Bug,根源出在你缓存的ReflectionProperty实例上。当你把ReflectionProperty对象存在静态属性里复用后,后续给对应属性赋值时,PHP内部的内存管理出现了问题,导致ReflectionType的名称被意外覆盖成了属性值里的字符串。至于为什么特定长度的字符串、var_export会触发?那是因为这些操作刚好凑成了触发内存覆盖的条件,属于Bug的偶发性表现。
顺带提一句你代码里的潜在小坑:原来的private static $publicProps是BaseClass的私有静态属性,所有子类都会共享这个缓存(如果有多个子类的话还会互相覆盖),不过这不是这次问题的核心,但后续可以优化。
接下来给你几个解决方案,既能保留缓存的性能,又能避开这个Bug:
方案1:缓存类型名称而非ReflectionProperty实例
既然问题出在ReflectionProperty实例被意外修改,那我们只缓存属性名和对应的类型字符串就好,完全绕开反射实例的问题:
class BaseClass { /** @var array<string, array<string, string>> 按类名缓存属性名=>类型名 */ private static $propTypes = []; /** * 获取并缓存子类的公共属性类型映射 * @return array<string, string> * @throws ReflectionException */ private function getPropTypes(){ $concreteClass = get_class($this); if (!isset(static::$propTypes[$concreteClass])) { $refClass = new ReflectionClass($concreteClass); $props = $refClass->getProperties(ReflectionProperty::IS_PUBLIC); $typeMap = []; foreach ($props as $prop) { $typeMap[$prop->getName()] = $prop->getType()->getName(); } static::$propTypes[$concreteClass] = $typeMap; } return static::$propTypes[$concreteClass]; } public function __construct($propArr) { $concreteClass = get_class($this); echo "Creating new instance of $concreteClass<br>"; $propTypes = $this->getPropTypes(); foreach ($propTypes as $propName => $propClass) { $propValue = $propArr[$propName]; $propValueStr = var_export($propValue, true); echo "---Setting: ->$propName = new $propClass($propValueStr)<br>"; assert($propClass === "DummyClass", "$propClass !== DummyClass"); $refClass = new ReflectionClass($propClass); $this->$propName = $refClass->newInstanceArgs([$propValue]); } } }
这个方案既保留了缓存的优势,又彻底避开了反射实例的内存问题,是最可靠的解决方式。
方案2:修复静态缓存的子类隔离问题(如果需要缓存ReflectionProperty)
如果你非要缓存ReflectionProperty实例,首先得修复原来的缓存逻辑——原来的私有静态属性会被所有子类共享,应该按类名做键来缓存:
/** @var array<string, ReflectionProperty[]> 按类名缓存反射属性 */ private static $publicProps = []; private function getPublicProps(){ $concreteClass = get_class($this); if (!isset(static::$publicProps[$concreteClass])) { static::$publicProps[$concreteClass] = (new ReflectionClass($concreteClass))->getProperties(ReflectionProperty::IS_PUBLIC); } return static::$publicProps[$concreteClass]; }
但注意,这个方案只是修复了缓存的子类隔离问题,不一定能解决反射类型被覆盖的Bug,所以还是方案1更稳妥。
方案3:升级PHP版本
这类内存相关的反射Bug通常会在后续PHP版本中被修复,比如升级到PHP 8.1+的最新稳定版,大概率就能解决这个问题。
最后再确认一下:你的直觉完全正确,这就是PHP的Bug,不是你的代码逻辑问题。用方案1既可以避免重复反射,又能彻底解决这个诡异的问题。
内容的提问来源于stack exchange,提问作者JS_Riddler

