本文目录导读:

这是一个非常实用的企业级功能需求,在PHP项目中实现合同比对,核心目标通常是快速找出两个或多个文档(如Word、PDF、纯文本)之间的差异。
由于PHP本身不擅长解析Word和PDF的复杂格式,常见的实现思路是:
- 提取文本内容:将合同从PDF/Word转为纯文本。
- 进行文本比对:使用类似Git Diff的算法(如LCS,最长公共子序列)找出差异。
- 展示结果:高亮显示删除、新增、修改的部分。
下面介绍几种具体的实现方案,按技术难度和效果排序。
基于文本比对库(最推荐,适合大多数场景)
该方案利用现成的PHP库,负责从文件中提取文本,再用专门差异算法库进行比对。
核心依赖库
你需要至少两个库:
- 文本提取库:
- PDF:
smalot/pdfparser(稳定,社区广) 或setasign/fpdi(处理表单)。 - Word:
phpoffice/phpword(可以读取docx)。
- PDF:
- 差异比对库:
finediff:一个非常优秀的PHP库,专门用于文本的逐行、逐词甚至逐字符比较,并能输出HTML高亮。
实现步骤
Step 1:安装依赖 (Composer)
composer require smalot/pdfparser composer require phpoffice/phpword composer require cogpowered/finediff
Step 2:提取文本
<?php
require 'vendor/autoload.php';
use Smalot\PdfParser\Parser as PdfParser;
use PhpOffice\PhpWord\IOFactory as WordFactory;
function extractText($filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
switch ($extension) {
case 'pdf':
$parser = new PdfParser();
$pdf = $parser->parseFile($filePath);
return $pdf->getText();
case 'docx':
$phpWord = WordFactory::load($filePath);
$text = '';
foreach ($phpWord->getSections() as $section) {
$elements = $section->getElements();
foreach ($elements as $element) {
if (method_exists($element, 'getText')) {
$text .= $element->getText() . "\n";
}
}
}
return $text;
case 'txt':
return file_get_contents($filePath);
default:
throw new \Exception('Unsupported file type: ' . $extension);
}
}
// 用法
$oldText = extractText('contract_v1.pdf');
$newText = extractText('contract_v2.docx');
Step 3:比对并输出高亮HTML
<?php
use cogpowered\FineDiff\Diff;
use cogpowered\FineDiff\Render\Html;
function compareContracts($oldText, $newText) {
// 使用逐词比对,更精细
$granularity = \cogpowered\FineDiff\Delimiters::paragraphs() . \cogpowered\FineDiff\Delimiters::sentences() . \cogpowered\FineDiff\Delimiters::words();
$diff = new Diff($granularity);
$renderer = new Html();
// 生成HTML,
// <ins> 表示新增内容
// <del> 表示删除内容
$result = $renderer->process($oldText, $newText);
return $result;
}
$htmlResult = compareContracts($oldText, $newText);
// 在前端显示时,记得给 <ins> 和 <del> 设置CSS样式
// ins { background-color: #d4fcbc; text-decoration: none; }
// del { background-color: #fbb; }
优点:纯PHP实现,部署简单,不依赖外部服务,效果精细(支持逐词比对)。 缺点:对复杂PDF(扫描件、表格)文本提取效果较差,需结合OCR。
调用外部命令行工具(最精确,适合专业文档)
对于复杂的Word格式(如有表格、页眉页脚)或扫描件PDF,纯PHP库提取文本会丢失格式信息,这时,调用专业工具更可靠。
工具组合
- 文档转文本:
pandoc(瑞士军刀,几乎支持所有格式互转) 或Apache Tika(内容分析工具包)。 - 差异比对:
diff(Linux自带) 或git diff。 - 可选:
pdftotext(来自Poppler工具集,专门用于PDF)。
实现步骤
Step 1:服务器安装工具
# Ubuntu sudo apt-get install pandoc poppler-utils # macOS brew install pandoc poppler
Step 2:PHP调用执行
<?php
function extractTextWithPandoc($filePath) {
$extension = strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
if ($extension === 'pdf') {
// PDF推荐用pdftotext,更准确
$output = shell_exec("pdftotext " . escapeshellarg($filePath) . " -");
} else {
// Word或其他用pandoc
$output = shell_exec("pandoc " . escapeshellarg($filePath) . " -t plain --wrap=none");
}
return $output;
}
function diffTexts($oldText, $newText) {
// 将文本写入临时文件
$tmpOld = tempnam(sys_get_temp_dir(), 'diff_old');
$tmpNew = tempnam(sys_get_temp_dir(), 'diff_new');
file_put_contents($tmpOld, $oldText);
file_put_contents($tmpNew, $newText);
// 执行系统diff,生成统一格式
$diffOutput = shell_exec("diff -u " . escapeshellarg($tmpOld) . " " . escapeshellarg($tmpNew));
unlink($tmpOld);
unlink($tmpNew);
return $diffOutput;
}
// 用法
$oldText = extractTextWithPandoc('contract_v1.pdf');
$newText = extractTextWithPandoc('contract_v2.docx');
$diffResult = diffTexts($oldText, $newText);
// 解析 diff 输出,转换为HTML
// 可以自己写一个简单解析器,或者用现成的库(如 PHP-Diff)
优点:文本提取精度最高,能处理复杂版式。 缺点:依赖服务器环境,需要安装外部软件,安全性需注意(防止命令注入)。
使用云服务API(最省心,适合SaaS应用)
如果不想折腾格式解析,可以直接调用云服务商提供的文档比对API。
- 腾讯云-文档比对:提供专门API,直接上传两份文档,返回JSON格式的差异列表。
- 钉钉/阿里云-文档服务:也有类似能力。
基本流程(伪代码):
// 1. 上传文件
$response = httpPost('https://api.xxx.com/document/compare', [
'file1' => new CURLFile('contract_v1.pdf'),
'file2' => new CURLFile('contract_v2.docx')
]);
// 2. 获取结果(通常包含每个段落的变更类型:新增、删除、修改、无变化)
$result = json_decode($response, true);
// 3. 根据API返回的坐标或文本片段,在前端渲染差异
优点:完全不关心文件格式解析,效果好。 缺点:需要付费、依赖网络、合同内容可能涉及数据隐私。
纯前端处理(减轻服务器压力)
如果合同文本内容不大且均为.txt或.md格式,可以在浏览器端用JavaScript完成比对,PHP仅负责提供原始文本。
- 前端库:
diff(npm包),jsdiff。 - 效果:用户操作完全在浏览器内完成,服务器只做文件上传和存储。
总结与建议
| 方案 | 适用场景 | 复杂度 | 效果 | 推荐指数 |
|---|---|---|---|---|
| PHPPHP库 | 常规合同,非扫描件,对格式无要求 | 低 | 良好(文本层面) | ⭐⭐⭐⭐⭐ |
| 命令行工具 | 复杂格式,需要绝对精确 | 中 | 极佳 | ⭐⭐⭐⭐ |
| 云API | 追求稳定性,有预算,数据不敏感 | 低 | 极佳(带格式) | ⭐⭐⭐ |
| 纯前端 | 纯文本,轻量级场景 | 低 | 一般 | ⭐⭐⭐ |
对于大多数PHP 项目,建议采用方案一:FineDiff + smalot/pdfparser + PHPWord。 它平衡了效果、部署成本和代码可控性,如果后续发现PDF提取文本不准确(例如扫描件),再为特定文件类型切换到方案二(调用pdftotext)或集成OCR(Tesseract)即可。