PHP中如何通过反射区分非类型类属性的null默认值与无默认值
非类型类属性默认值的反射区分方案
问题本质
PHP 针对非类型的类属性有隐式默认值规则:只要完成声明,无论是否显式书写= null,内核都会自动将其默认值设置为null,所以这两种写法在底层的默认值存储逻辑上完全一致,原生的ReflectionProperty::hasDefaultValue()和ReflectionProperty::getDefaultValue()天然无法区分两者。
解决方案
方案1:针对PHP 7.4+有类型属性(推荐)
如果可以调整代码为属性增加类型声明,不需要额外处理即可直接通过原生反射区分:有类型的属性不会被隐式赋予null默认值,未显式设置默认值的属性调用ReflectionProperty::hasDefaultValue()会直接返回false。
示例:
class B { public string $foo; // 无默认值 public ?string $bar = null; // 显式设置null默认值 } $rc = new ReflectionClass(B::class); var_dump($rc->getProperty('foo')->hasDefaultValue()); // bool(false) var_dump($rc->getProperty('bar')->hasDefaultValue()); // bool(true)
方案2:源码解析适配非类型属性
如果不能修改原有类的属性定义,可以通过PHP内置的token_get_all()解析类的源代码,判断属性定义段是否有显式的= null声明:
function hasExplicitNullDefault(ReflectionProperty $prop): bool { $declaringClass = $prop->getDeclaringClass(); $sourcePath = $declaringClass->getFileName(); if (!is_file($sourcePath)) { throw new RuntimeException('无法读取类的源代码文件'); } $tokens = token_get_all(file_get_contents($sourcePath)); $targetPropName = $prop->getName(); $matchedProp = false; foreach ($tokens as $token) { // 匹配目标属性名 if (is_array($token) && $token[0] === T_STRING && $token[1] === $targetPropName) { $matchedProp = true; } // 匹配到属性后查找显式null声明 if ($matchedProp) { if (is_array($token) && $token[0] === T_NULL) { return true; } // 遇到属性定义结束符终止匹配 if ($token === ';') { break; } } } return false; } // 测试示例类 class A { public $foo; public $bar = null; } $rc = new ReflectionClass(A::class); var_dump(hasExplicitNullDefault($rc->getProperty('foo'))); // bool(false) var_dump(hasExplicitNullDefault($rc->getProperty('bar'))); // bool(true)
注意事项
源码解析方案仅适用于可读取到类源码文件的场景,无法适配内置类、加密/混淆后的类。
内容的提问来源于stack exchange,提问作者Martin Heralecký
相关产品推荐
相关产品推荐

