A modern PHP library to generate, render and apply diffs, featuring advanced algorithms, versatile rendering, and full patching support.
- Myers Diff Algorithm (default): Fast and accurate line-by-line and word-by-word diffing (O(ND)).
- LCS Diff Algorithm: Opt-in Longest Common Subsequence engine (O(MN) time and memory) for deterministic academic use cases.
- Binary Detection: Automatic detection and rejection of binary content.
Visualize and format diffs for any output medium:
- Unified Diff Parsing: Parse standard unified diff patches into objects.
- Patch Application: Apply patches to files with "fuzz" factor support.
- Multi-file Bundles: Handle complex patches affecting multiple files.
- PHP 8.3 or higher
composer require alto/code-diffuse Alto\Code\Diff\Diff;
use Alto\Code\Diff\Renderer\UnifiedRenderer;
$old = "line1\nline2\nline3\n";
$new = "line1\nline2 modified\nline3\n";
$result = Diff::build()->compare($old, $new);
$renderer = new UnifiedRenderer('old.txt', 'new.txt');
echo $renderer->render($result);Output:
--- old.txt
+++ new.txt
@@ -1,3 +1,3 @@
line1
-line2
+line2 modified
line3$result = Diff::build()
->withWordDiff()
->compare($old, $new);use Alto\Code\Diff\Renderer\HtmlRenderer;
$renderer = new HtmlRenderer(
showLineNumbers: true,
wrapLines: false,
classPrefix: 'diff-'
);
echo $renderer->render($result);use Alto\Code\Diff\Renderer\JsonRenderer;
$renderer = new JsonRenderer(prettyPrint: true);
echo $renderer->render($result);use Alto\Code\Diff\Renderer\AnsiSideBySideRenderer;
$renderer = new AnsiSideBySideRenderer(
showLineNumbers: true,
width: 120
);
echo $renderer->render($result);Control how many unchanged lines to show around changes:
$result = Diff::build()
->contextLines(5) // Default is 3
->compare($old, $new);Ignore whitespace differences:
$result = Diff::build()
->ignoreWhitespace()
->compare($old, $new);Set maximum input size (default 5MB):
$result = Diff::build()
->maxBytes(10_000_000) // 10MB
->compare($old, $new);use Alto\Code\Diff\Patch\UnifiedParser;
$patch = <<<'PATCH'
--- old.txt
+++ new.txt
@@ -1,3 +1,3 @@
line1
-line2
+line2 modified
line3
PATCH;
$parser = new UnifiedParser();
$bundle = $parser->parse($patch);
foreach ($bundle->files() as $file) {
echo "File: {$file->oldPath} -> {$file->newPath}\n";
echo "Hunks: " . count($file->result->hunks()) . "\n";
}use Alto\Code\Diff\Patch\PatchApplier;
$original = "line1\nline2\nline3\n";
$applier = new PatchApplier();
$patched = $applier->apply($original, $patch);
echo $patched;
// Output: line1\nline2 modified\nline3\nNote:
PatchApplier::apply()accepts a single-file patch. For multi-file diffs, parse the patch and callapplyBundle()instead.
$applier = new PatchApplier(fuzz: 2);
$patched = $applier->apply($original, $patch);use Alto\Code\Diff\Model\DiffBundle;
$files = [
'file1.txt' => "content1\n",
'file2.txt' => "content2\n",
];
$applier = new PatchApplier();
$patchedFiles = $applier->applyBundle($files, $bundle);use Alto\Code\Diff\Patch\UnifiedEmitter;
$result = Diff::build()->compare($old, $new);
$emitter = new UnifiedEmitter();
$patch = $emitter->emit($result);use Alto\Code\Diff\Model\DiffBundle;
use Alto\Code\Diff\Model\DiffFile;
$files = [
new DiffFile('file1.txt', 'file1.txt', $result1),
new DiffFile('file2.txt', 'file2.txt', $result2),
];
$bundle = new DiffBundle($files);
$emitter = new UnifiedEmitter();
$patch = $emitter->emit($bundle);new UnifiedRenderer(
oldLabel: 'a/file.txt', // Label for old version
newLabel: 'b/file.txt' // Label for new version
);new HtmlRenderer(
showLineNumbers: true, // Show line numbers
wrapLines: false, // Wrap long lines
classPrefix: 'diff-' // CSS class prefix
);new JsonRenderer(
prettyPrint: true // Format with indentation
);new AnsiSideBySideRenderer(
showLineNumbers: true, // Show line numbers
width: 120 // Terminal width
);You can choose between the built-in engines or implement your own.
MyersDiffEngine (Default): Uses the O(ND) Myers algorithm. Best for most use cases, especially when differences are small.
LcsDiffEngine:
Uses the standard O(MN) LCS algorithm. Enable it explicitly with ->withEngine(new LcsDiffEngine()) only for small inputs, because its quadratic memory footprint is intended for controlled, academic scenarios.
use Alto\Code\Diff\Engine\LcsDiffEngine;
$result = Diff::build()
->withEngine(new LcsDiffEngine())
->compare($old, $new);use Alto\Code\Diff\Engine\DiffEngineInterface;
class MyCustomEngine implements DiffEngineInterface
{
public function diff(string $old, string $new, Options $opts): DiffResult
{
// Custom implementation
}
}
$result = Diff::build()
->withEngine(new MyCustomEngine())
->compare($old, $new);The library supports parsing git-style unified diffs with headers:
$patch = <<<'PATCH'
diff --git a/file.txt b/file.txt
index abcdef..123456 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
line1
-line2
+line2 modified
line3
PATCH;
$parser = new UnifiedParser();
$bundle = $parser->parse($patch);
// Access headers
$file = $bundle->files()[0];
$file->headers['diff']; // 'diff --git a/file.txt b/file.txt'
$file->headers['index']; // 'index abcdef..123456 100644'For more detailed information, please refer to the documentation in the docs/ directory:
- API Reference
- Architecture
- Basic Text Comparison
- File Comparison
- Git Integration
- Patch Operations
- Rendering Options
- Advanced Scenarios
- Examples
Run the test suite:
vendor/bin/phpunitRun tests with coverage:
vendor/bin/phpunit --coverage-textThis project is licensed under the MIT License.