PHPStan自定义规则中如何在静态分析场景下可靠检测类是否存在子类?
嘿,我之前在写PHPStan自定义规则、判断类能不能安全标记为readonly的时候,刚好碰到过一模一样的问题!毕竟PHP 8.2里父类要是标记了readonly,子类也必须跟着加,所以得先确认目标类有没有子类才行。
1. 最推荐:直接用PHPStan内置的反射API(不用自己造轮子)
其实你完全没必要自己去扫文件或者处理类映射——PHPStan在做静态分析的时候,已经遍历了所有你配置的要分析的文件,并且自动构建了完整的类继承关系图谱。你只需要通过它的内置API就能直接拿到子类列表:
在你的自定义规则的processNode方法里,可以这样写:
public function processNode(Node $node, Scope $scope): void { // 只处理类声明节点 if (!$node instanceof \PhpParser\Node\Stmt\Class_) { return; } // 获取当前类的反射实例 $classReflection = $scope->getClassReflection(); if (!$classReflection) { return; } // 直接获取所有子类! $subclasses = $classReflection->getSubclasses(); if (!empty($subclasses)) { // 存在子类,不能建议标记为readonly $this->nodeTypeResolver->addError( '该类存在子类,无法安全标记为readonly', $node, 'YourRuleNamespace.HasSubclasses' ); } }
这里要注意,getSubclasses()返回的是当前PHPStan分析范围内的所有子类——也就是只要是你在phpstan.neon里配置的分析路径/命名空间下的子类,都会被准确找到,完全不需要自己处理任何文件扫描或者类映射的逻辑,这是最可靠的方案。
2. 特殊场景下的备选方案:自定义文件扫描
如果你的项目有一些不在PHPStan默认分析范围内的代码,但你又需要检测这些代码里是否存在目标类的子类,那才需要考虑自定义扫描逻辑:
- 别用正则解析PHP文件,太容易出错(比如注释里的类名、字符串里的类名会被误判),推荐用
nikic/php-parser来解析文件,它能准确识别出代码里的类声明和继承关系。 - 扫描逻辑大概是:遍历目标目录下的所有PHP文件,用parser解析出每个类的父类,然后构建一个
[父类全限定名 => 子类列表]的映射表,之后就可以通过这个表查询目标类有没有子类。 - 但这种情况真的很少见,一般项目都会把所有业务代码加到PHPStan的分析范围内,所以优先用内置API就够了。
3. 别碰Composer的类映射
之前你考虑的用Composer的autoload_classmap.php或者强制composer dump-autoload -o --classmap-authoritative的方案,我个人非常不推荐:
- PSR-4自动加载模式下,默认生成的类映射不会包含所有项目类,只能覆盖已经被加载过的类。
- 强制生成权威类映射在开发环境太麻烦,代码一更新就得重新dump,而且很容易因为类映射过时导致检测错误。
总结
99%的场景下,直接用PHPStan内置的ClassReflection::getSubclasses()方法就够了——这是官方为静态分析场景设计的API,既可靠又省心。只有当你需要检测PHPStan分析范围外的代码时,再考虑用nikic/php-parser做自定义扫描,别用正则或者Composer类映射这种容易踩坑的方案。
内容来源于stack exchange

