Psalm项目中Callmap文件编辑指南
什么是Callmap
在静态分析工具Psalm中,Callmap(调用映射)是一种特殊的数据结构,用于描述PHP函数和方法的签名信息。它本质上是一个PHP数组文件,详细记录了每个函数/方法的参数类型、返回类型以及其他调用特征。
Callmap文件结构
Psalm的Callmap系统由多个文件组成,主要分为三类:
- 基础Callmap文件:位于
dictionaries/autogen/
目录下,这些文件是通过反射自动生成的 - 覆盖Callmap文件:位于
dictionaries/override/
目录下,包含手动维护的函数签名 - Delta文件:同样位于
dictionaries/override/
目录下,记录不同PHP版本间的函数签名变化
如何修改Callmap
基本修改流程
- 定位需要修改的函数签名
- 在
dictionaries/override/CallMap.php
中找到对应函数(若无则添加) - 如果函数在不同PHP版本中有变化,还需修改对应的Delta文件
- 运行生成脚本更新最终使用的Callmap文件
生成Callmap文件
修改完成后,需要运行以下命令重新生成Callmap:
php bin/gen_callmap.php
如需完全重新生成基础Callmap(包括从PHP核心和扩展中提取的类型信息),则需要运行:
bin/gen_callmap.sh
Callmap格式详解
Callmap中的每个函数条目都遵循特定格式:
'函数名' => [
返回类型,
参数1类型,
参数2类型,
// ...
]
特殊参数标记
- 可选参数:在参数名后加
=
,如$param=
- 引用参数:使用
&
前缀,具体分为:&r_
:只读引用&w_
:只写引用&rw_
:读写引用
- 可变参数:使用
...
前缀
函数别名
对于具有多态行为的函数,可以使用函数别名来表示不同的调用方式。例如:
'version_compare' => [
'bool',
'version1' => 'string',
'version2' => 'string',
'operator' => '\'!=\'|\'<\'|\'<=\'|\'<>\'|\'=\'|\'==\'|\'>\'|\'>=\'|\'eq\'|\'ge\'|\'gt\'|\'le\'|\'lt\'|\'ne\'|null',
],
'version_compare\'1' => [
'int',
'version1' => 'string',
'version2' => 'string',
]
这表示version_compare
函数:
- 当提供
operator
参数时返回bool
- 不提供时返回
int
Delta文件详解
Delta文件记录PHP版本间的函数签名变化,文件名格式为CallMap_<主版本><次版本>_delta.php
,包含三个部分:
- added:该版本新增的函数
- removed:该版本移除的函数
- changed:该版本发生变化的函数
变化记录格式
'changed' => [
'函数名' => [
'old' => [旧签名],
'new' => [新签名]
]
]
实际应用示例
案例1:添加新函数
假设要在PHP 8.1中添加array_is_list()
函数:
- 在
CallMap_81_delta.php
的added
部分添加该函数 - 在
CallMap.php
中也添加该函数
案例2:修正函数签名
如果发现某个函数签名一直不正确:
- 直接在
CallMap.php
中修正 - 如果签名是在某个PHP版本中变化的,还需在对应版本的Delta文件中记录变化
案例3:处理版本间变化
如果函数签名在PHP 8.0中发生了变化:
- 在
CallMap_80_delta.php
的changed
部分记录变化 - 在
CallMap.php
中更新为最新签名
最佳实践
-
复杂函数处理:对于行为复杂的函数,考虑:
- 在
stubs/
目录下创建模板存根 - 或创建自定义返回类型提供器
- 在
-
验证修改:修改后务必运行生成脚本并检查是否有验证错误
-
保持历史一致性:确保Delta文件中的变化记录与实际版本变更一致
-
参数处理:注意特殊参数的标记方式,确保正确表达参数特性
通过正确维护Callmap,可以显著提高Psalm对PHP代码分析的准确性,特别是对于内置函数和方法的行为判断。理解Callmap的工作原理和编辑规范,是参与Psalm项目开发的重要基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考