wackowiki/diff
Composer 安装命令:
composer require wackowiki/diff
包简介
PHP diff library modernized for PHP 8.5
README 文档
README
A modernized PHP diff library originally derived from WackoWiki's PHP port of
Geoffrey T. Dairiki's Algorithm::Diff Perl module, itself based on Ned Konz's
work and the GNU diff algorithm from François Pinard (1992).
This fork modernizes the codebase for PHP 8.3+ with strict types, proper namespacing, and a full PHPUnit 11 test suite.
Features
- Strict types throughout — full PHP 8.3+ compatibility
- Word-level diffs — for inline comparison via
Diff\Side - Line-level diffs — classic GNU diff format via
Diff\Diff+Diff\DiffFormatter - Fully tested — 95+ unit tests covering all classes
- Zero dependencies — pure PHP, no external libraries
- GPL-2.0-or-later — open source license
Requirements
- PHP 8.3 or higher
- Composer (for installation and testing)
- PHPUnit 11 (development only)
Installation
Install via Composer:
composer require wackowiki/diff
Or add to your composer.json:
{
"require": {
"wackowiki/diff": "^1.0"
}
}
Usage
1. Line-Level Diff (Classic)
Compare two files line-by-line and output GNU diff format:
<?php declare(strict_types=1); require_once 'vendor/autoload.php'; use Diff\Diff; use Diff\DiffFormatter; $fromLines = [ "The quick brown fox", "jumps over the lazy dog", ]; $toLines = [ "The quick brown fox", "jumps over the lazy cat", "and runs away", ]; // Compute the diff $diff = new Diff($fromLines, $toLines); // Format as classic GNU diff output $formatter = new DiffFormatter(); $output = $formatter->format($diff); echo $output;
Output:
1,2c1,3
< The quick brown fox
< jumps over the lazy dog
---
> The quick brown fox
> jumps over the lazy cat
> and runs away
2. Inspect Diff Operations
Each Diff object exposes its edit operations directly:
<?php declare(strict_types=1); use Diff\Diff; use Diff\Engine\DiffOpCopy; use Diff\Engine\DiffOpAdd; use Diff\Engine\DiffOpDelete; use Diff\Engine\DiffOpChange; $diff = new Diff(['a', 'b', 'c'], ['a', 'X', 'c']); foreach ($diff->edits as $edit) { echo match (true) { $edit instanceof DiffOpCopy => "COPY: " . implode(', ', $edit->orig) . PHP_EOL, $edit instanceof DiffOpAdd => "ADD: " . implode(', ', $edit->final) . PHP_EOL, $edit instanceof DiffOpDelete => "DEL: " . implode(', ', $edit->orig) . PHP_EOL, $edit instanceof DiffOpChange => "CHANGE: -[" . implode(', ', $edit->orig) . "] +[" . implode(', ', $edit->final) . "]" . PHP_EOL, }; }
Output:
COPY: a
CHANGE: -[b] +[X]
COPY: c
3. Word-Level Diff (Inline Comparison)
For word-level diffs (useful for inline comparison UIs):
<?php declare(strict_types=1); use Diff\Side; use Diff\Diff; use Diff\DiffFormatter; $textA = "The quick brown fox"; $textB = "The slow brown fox"; $sideA = new Side($textA); $sideB = new Side($textB); // Split into words for comparison $bodyA = ''; $sideA->split_file_into_words($bodyA); $bodyB = ''; $sideB->split_file_into_words($bodyB); // Compute word-level diff $diff = new Diff(explode("\n", $bodyA), explode("\n", $bodyB)); // Format and decode for inline markup $formatter = new DiffFormatter(); $output = new Side($formatter->format($diff)); $resyncLeft = $resyncRight = 0; $outputText = ''; $countTotalRight = $sideB->getposition(); $sideA->init(); $sideB->init(); while (!$output->isend()) { $output->skip_line(); if ($output->decode_directive_line()) { $argument = $output->getargument(); $letter = $output->getdirective(); $resyncLeft = match ($letter) { 'a' => $argument[0], 'd', 'c' => $argument[0] - 1, }; $resyncRight = match ($letter) { 'a' => $argument[2] - 1, 'd' => $argument[2], 'c' => $argument[2] - 1, }; $sideA->skip_until_ordinal($resyncLeft); $sideB->copy_until_ordinal($resyncRight, $outputText); if ($letter === 'd' || $letter === 'c') { $sideA->copy_whitespace($outputText); $outputText .= '**[DELETED]** '; $sideA->copy_word($outputText); $sideA->copy_until_ordinal($argument[1], $outputText); } if ($letter === 'a' || $letter === 'c') { $sideB->copy_whitespace($outputText); $outputText .= ' **[INSERTED]** '; $sideB->copy_word($outputText); $sideB->copy_until_ordinal($argument[3], $outputText); } } } $sideB->copy_until_ordinal($countTotalRight, $outputText); echo $outputText;
Output:
The [DELETED]quick **[INSERTED]**slow brown fox
4. Empty Inputs
The library safely handles empty inputs:
<?php use Diff\Diff; $diff = new Diff([], []); assert($diff->edits === []); // No edits $diff = new Diff([], ['new content']); // Returns one DiffOpAdd operation
Architecture
Class Hierarchy
Diff\Diff
└── uses Diff\Engine\DiffEngine (computes LCS-based diff)
└── produces Diff\Engine\DiffOp* objects
Diff\DiffFormatter
└── formats Diff\Diff into classic GNU diff output
Diff\Side
└── parses diff output / splits text into words
Core Classes
| Class | Purpose |
|---|---|
Diff\Diff |
Container for edit operations between two sequences |
Diff\DiffFormatter |
Formats a Diff into GNU-style output |
Diff\Side |
Parser/iterator for word-level diff operations |
Diff\Engine\DiffEngine |
LCS algorithm implementation |
Diff\Engine\DiffOp |
Base class for diff operations |
Diff\Engine\DiffOpCopy |
Unchanged lines |
Diff\Engine\DiffOpAdd |
Added lines |
Diff\Engine\DiffOpDelete |
Deleted lines |
Diff\Engine\DiffOpChange |
Replaced lines |
Testing
This library has a comprehensive PHPUnit 11 test suite covering all 9 classes.
Run Tests
# Install dev dependencies composer install # Run all tests ./vendor/bin/phpunit # Run with verbose output ./vendor/bin/phpunit --testdox # Run specific test class ./vendor/bin/phpunit tests/SideTest.php # Run with coverage report (requires Xdebug or PCOV) ./vendor/bin/phpunit --coverage-text
Test Coverage
The test suite includes:
- 95+ test cases covering all public APIs
- Edge cases: empty inputs, unicode, state reset between instances
- Algorithm validation: LCS behavior, duplicate lines, boundary detection
- Strict types verification: ensures no
nullcoercion regressions
Development
Setup
# Clone the repository git clone https://github.com/wackowiki/diff.git cd diff # Install dependencies composer install
Project Structure
.
├── src/
│ ├── Diff.php
│ ├── DiffFormatter.php
│ ├── Side.php
│ └── Engine/
│ ├── DiffEngine.php
│ ├── DiffOp.php
│ ├── DiffOpAdd.php
│ ├── DiffOpChange.php
│ ├── DiffOpCopy.php
│ └── DiffOpDelete.php
├── tests/
│ ├── bootstrap.php
│ ├── DiffTest.php
│ ├── DiffFormatterTest.php
│ ├── SideTest.php
│ └── Engine/
│ ├── DiffEngineTest.php
│ ├── DiffOpTest.php
│ ├── DiffOpAddTest.php
│ ├── DiffOpChangeTest.php
│ ├── DiffOpCopyTest.php
│ └── DiffOpDeleteTest.php
├── composer.json
├── phpunit.xml
└── README.md
Coding Standards
- PHP 8.3+ strict types — every file starts with
declare(strict_types=1); - PSR-4 — namespacing follows
Diff\andDiff\Tests\conventions - Type declarations — all parameters and returns are typed
- No dependencies — pure PHP, no runtime dependencies
Credits & History
This library traces its lineage through several iterations:
Original Authors
- François Pinard (1992) — Original GNU
diffalgorithm - Geoffrey T. Dairiki (2000-2001) — Perl
Algorithm::Diffimplementation - David DELON (2002-2004) — Initial PHP port
- Patrick PAUL (2002) — Contributor to PHP port
- Eric FELDSTEIN (2003) — Contributor to PHP port
Modernization (2024+)
The codebase was modernized for PHP 8.3+ with:
- Strict type declarations throughout
- Proper namespacing under
Diff\ - Complete PHPUnit 11 test suite
- Modernized method signatures and return types
License
GNU General Public License v2.0 or later — see LICENSE for details.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/improvement) - Add tests for any new functionality
- Ensure all tests pass (
./vendor/bin/phpunit) - Submit a pull request
Support
- 🐛 Issues: https://github.com/wackowiki/diff/issues
- 💬 Discussions: https://github.com/wackowiki/diff/discussions
- 📖 WackoWiki: https://wackowiki.org
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 1
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: GPL-2.0-or-later
- 更新时间: 2026-07-08