alto / code-diff
Generate, render and apply code diffs. Myers algorithm, versatile rendering (HTML, JSON, ANSI), and full patching support.
Fund package maintenance!
Requires
- php: ^8.3
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.92
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^12.5
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A modern PHP library to generate, render and apply diffs, featuring advanced algorithms, versatile rendering, and full patching support.
Features
Advanced Diff Algorithms
- 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.
Versatile Rendering
Visualize and format diffs for any output medium:
HTML Output
ANSI Side-by-Side
Full Patching Support
- 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.
Requirements
- PHP 8.3 or higher
Installation
composer require alto/code-diff
Quick Start
Basic Diff
use 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
Word-Level Diff
$result = Diff::build() ->withWordDiff() ->compare($old, $new);
HTML Output
use Alto\Code\Diff\Renderer\HtmlRenderer; $renderer = new HtmlRenderer( showLineNumbers: true, wrapLines: false, classPrefix: 'diff-' ); echo $renderer->render($result);
JSON Output
use Alto\Code\Diff\Renderer\JsonRenderer; $renderer = new JsonRenderer(prettyPrint: true); echo $renderer->render($result);
ANSI Side-by-Side Output
use Alto\Code\Diff\Renderer\AnsiSideBySideRenderer; $renderer = new AnsiSideBySideRenderer( showLineNumbers: true, width: 120 ); echo $renderer->render($result);
Configuration Options
Context Lines
Control how many unchanged lines to show around changes:
$result = Diff::build() ->contextLines(5) // Default is 3 ->compare($old, $new);
Ignore Whitespace
Ignore whitespace differences:
$result = Diff::build() ->ignoreWhitespace() ->compare($old, $new);
Size Limits
Set maximum input size (default 5MB):
$result = Diff::build() ->maxBytes(10_000_000) // 10MB ->compare($old, $new);
Parsing and Applying Patches
Parse a Unified Diff
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"; }
Apply a Patch
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\n
Note:
PatchApplier::apply()accepts a single-file patch. For multi-file diffs, parse the patch and callapplyBundle()instead.
Apply Patch with Fuzz Factor
$applier = new PatchApplier(fuzz: 2); $patched = $applier->apply($original, $patch);
Apply Patch to Multiple Files
use Alto\Code\Diff\Model\DiffBundle; $files = [ 'file1.txt' => "content1\n", 'file2.txt' => "content2\n", ]; $applier = new PatchApplier(); $patchedFiles = $applier->applyBundle($files, $bundle);
Emitting Unified Diffs
From DiffResult
use Alto\Code\Diff\Patch\UnifiedEmitter; $result = Diff::build()->compare($old, $new); $emitter = new UnifiedEmitter(); $patch = $emitter->emit($result);
From DiffBundle
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);
Renderer Options
UnifiedRenderer
new UnifiedRenderer( oldLabel: 'a/file.txt', // Label for old version newLabel: 'b/file.txt' // Label for new version );
HtmlRenderer
new HtmlRenderer( showLineNumbers: true, // Show line numbers wrapLines: false, // Wrap long lines classPrefix: 'diff-' // CSS class prefix );
JsonRenderer
new JsonRenderer( prettyPrint: true // Format with indentation );
AnsiSideBySideRenderer
new AnsiSideBySideRenderer( showLineNumbers: true, // Show line numbers width: 120 // Terminal width );
Advanced Usage
Custom Diff Engine
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);
Implementing a Custom Engine
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);
Working with Git Patches
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'
Documentation
- 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.
Contributing
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 qa
Changes to public behavior should include tests and documentation.
Support
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.
License
ALTO Code Diff is released by ALTO PHP under the MIT License.