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'- Installation: install the package and verify its requirements.
- Getting started: compare two strings and render a unified diff.
- Diffing: configure comparisons, inspect results, and select an engine.
- Rendering: produce unified text, HTML, JSON, or ANSI output.
- Formats: emit, parse, and apply unified patches.
- Errors: recover from rejected inputs and patches.
- Complete documentation: review the package scope and every guide.
Contributions of all kinds are welcome. Visit the project on GitHub to report a bug, suggest a feature, or open a pull request.
Before submitting code, run:
# Runs PHP CS Fixer, PHPStan, and PHPUnit
composer qaChanges to public behavior should include tests and documentation.
ALTO Code Diff is open source and independently maintained by Simon André. If it is useful to your work, you can support its continued development through GitHub Sponsors.
Sharing the package or starring it on GitHub also helps.
ALTO Code Diff is released by ALTO PHP under the MIT License.