本文目录导读:

- 方案一:基于 Token 的代码哈希(适用于纯文本抄袭)
- 方案二:抽象语法树 (AST) 比对(适用于逻辑结构抄袭)
- 方案三:基于 CS(克隆检测)工具的集成方案(生产环境推荐)
- 方案四:混合比对策略(高精度,推荐用于关键检测)
- 为什么不推荐只用简单的文本 diff(如
similar_text)? - 如果你的具体需求是
针对 PHP 项目的相似度比对和查重检测,通常需要结合 代码结构分析、逻辑抽象 和 文本相似度算法 来综合判断,单纯的字符串匹配容易误判(如变量名不同但逻辑相同)。
以下是几种主流且有效的实现方案,从简单到复杂,按需选择:
基于 Token 的代码哈希(适用于纯文本抄袭)
这是最基础的方法,将代码转化为标记流后进行比对,能抵抗格式(空格、换行)和部分注释的干扰。
核心原理
- 词法分析:将 PHP 代码解析成 Token 数组(使用
token_get_all函数)。 - 归一化:剔除所有空白符、注释;将所有变量名(T_VARIABLE)替换为统一占位符(如
$VAR),将所有字符串(T_CONSTANT_ENCAPSED_STRING)替换为占位符(如STR)。 - 哈希分片:使用 MinHash 或 SimHash 对归一化后的 Token 序列生成指纹,计算 Jaccard 相似度。
PHP 实现示例(核心逻辑)
function phpCodeSimHash($code) {
$tokens = token_get_all($code);
$normalized = [];
foreach ($tokens as $token) {
if (is_array($token)) {
$name = $token[0];
// 忽略注释、空白
if (in_array($name, [T_COMMENT, T_DOC_COMMENT, T_WHITESPACE])) continue;
// 归一化变量、方法名、字符串
if ($name === T_VARIABLE) {
$normalized[] = 'VAR';
} elseif ($name === T_STRING) { // 函数名、类名
$normalized[] = 'FUNC';
} elseif ($name === T_CONSTANT_ENCAPSED_STRING) {
$normalized[] = 'STR';
} else {
$normalized[] = $token[1]; // 保留关键字、运算符
}
} else {
$normalized[] = $token; // 标点符号如 {}();
}
}
// 使用 SimHash 计算指纹(需引入hash计算库)
return simhash(implode(' ', $normalized));
}
优点:速度快,适合大规模检测。
缺点:无法检测逻辑结构等价但语法重写的代码(如 if 改 switch,循环改递归)。
抽象语法树 (AST) 比对(适用于逻辑结构抄袭)
通过解析 PHP 语法树,比较其结构特征,可以识别将 for 改为 while、或者调整代码顺序的抄袭。
核心原理
- 使用
php-ast扩展或nikic/php-parser库生成 AST。 - 遍历 AST 节点,提取 结构向量(如:语句类型频率、控制流深度、操作符使用模式)。
- 计算两个结构向量之间的 余弦相似度 或 欧氏距离。
使用 nikic/php-parser 的快速示例
use PhpParser\{ParserFactory, NodeTraverser, NodeVisitorAbstract};
class StructureExtractor extends NodeVisitorAbstract {
public $features = ['if_count' => 0, 'for_count' => 0, 'assign_count' => 0, 'depth' => 0];
public function enterNode(Node $node) {
if ($node instanceof Node\Stmt\If_) $this->features['if_count']++;
if ($node instanceof Node\Expr\Assign) $this->features['assign_count']++;
// ... 可以统计更多结构特征
}
}
function comparePhpStructure($code1, $code2) {
$parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
$traverser = new NodeTraverser();
$visitor = new StructureExtractor();
$traverser->addVisitor($visitor);
try {
$ast1 = $parser->parse($code1);
} catch (\Exception $e){ return 0; }
$features1 = $traverser->traverse($ast1);
$visitor->features = ['if_count'=>0,'for_count'=>0,'assign_count'=>0];
$features2 = ...; // 解析第二份代码
// 计算余弦相似度
return cosineSimilarity($features1, $features2);
}
优点:抗重构能力强,能检测变量重命名、逻辑结构调整。
缺点:PHP语法复杂,解析大项目时性能较差。
基于 CS(克隆检测)工具的集成方案(生产环境推荐)
如果你需要处理整个 PHP 项目(含多个文件、框架代码),建议直接使用成熟的第三方工具,它们已经整合了词法分析、AST、以及机器学习模型。
推荐工具
-
JPlag (支持 PHP)
- 德国卡尔斯鲁厄理工学院开发,支持多种语言。
- 架构:将 PHP 代码通过内部解析器转换为 Token 流,然后使用 Greedy String Tiling 算法进行比对。
- 输出HTML报告,高亮相似代码段。
-
MOSS (Measure Of Software Similarity)
- 斯坦福大学开发,支持PHP。
- 原理:使用 Winnowing 算法(选取文档特征指纹),提交到服务器比对,适合做最终的查重验证,但需要网络权限且源代码需上传。
-
Simian
- 轻量级重复代码检测工具,支持PHP。
- 原理:基于行的令牌匹配,可以配置最小重复行数。
使用 JPlag 检测 PHP 项目示例
# 下载 JPlag.jar,运行命令比较两个目录 java -jar jplag.jar -l php /path/to/project1 /path/to/project2 -r /path/to/report
混合比对策略(高精度,推荐用于关键检测)
结合 代码结构 和 运行时行为 的指纹,能够精准识别深层抄袭(如重命名全部标识符、改写控制流但逻辑等价)。
流程设计
- 第一层:符号归一化 Hash 过滤
快速淘汰完全不同代码(相似度 < 30%),减少后续计算量。 - 第二层:AST 子树匹配
处理经过变量重命名和微小结构调整的代码(相似度 60%-80%)。 - 第三层:动态数据流分析(可选)
- 使用工具如 PHP Opcache 编译为 opcode。
- 比较 Opcode 序列的编辑距离 (Levenshtein Distance)。
- 如果操作码序列高度相似,则几乎可以判定为逻辑等价。
为什么不推荐只用简单的文本 diff(如 similar_text)?
- 误伤率高:第三方 PHP 框架(如 Laravel)的标准模板、以及 PHP 语法关键字(如
<?php,function)会产生大量重复。 - 无法抵抗格式化差异:缩进、换行、注释都会造成文本差异,但代码逻辑完全相同。
- 速度慢:对大型项目进行字符串矩阵比对 O(n²),不可行。
如果你的具体需求是
| 场景 | 推荐方案 |
|---|---|
| 教学作业快速查重 | 方案一 Token SimHash + JPlag 命令行 |
| 公司内部代码安全审计 | 方案二 AST结构向量 + 方案三 MOSS |
| 需要 100% 保证 没有漏报(高风险场景) | 方案四混合策略 + Opcode级比对 |
| 仅需重复代码块检测 | 使用 Simian 或 PHP CodeSniffer 的重复代码嗅探器 |
最终建议:对于 PHP 项目,除非是简单的函数或脚本,否则不要依赖单一算法。最佳实践是先使用 JPlag(免费且经过多年验证)得到一个初步结果,再对高相似度的对组进行人工 Review。