sebastian/diff
Standalone PHP diff library extracted from PHPUnit. Generate textual diffs between strings with configurable output builders (unified, strict unified, diff-only) or custom formats, and parse unified diffs into an object model for further processing.
Installation:
composer require sebastian/diff
For development-only use (e.g., tests):
composer require --dev sebastian/diff
First Use Case: Generate a unified diff between two strings:
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;
$differ = new Differ(new StrictUnifiedDiffOutputBuilder);
echo $differ->diff('old content', 'new content');
Where to Look First:
Differ (for generating diffs) and Parser (for parsing diffs).StrictUnifiedDiffOutputBuilder (default), DiffOnlyOutputBuilder (minimal output).$differ = new Differ(new StrictUnifiedDiffOutputBuilder(['header' => false]));
$diff = $differ->diff($expectedJson, $actualJson);
Log::error("JSON Mismatch:\n{$diff}");
'header' => false) for cleaner logs.use SebastianBergmann\Diff\Parser;
use Symfony\Component\Process\Process;
$gitDiff = (new Process(['git', 'diff', 'HEAD~1']))->getOutput();
$parser = new Parser();
$diffObject = $parser->parse($gitDiff);
foreach ($diffObject as $diff) {
foreach ($diff->getChunks() as $chunk) {
if ($chunk->getAddedLines() > 0) {
echo "Added: " . $chunk->getAddedLines() . " lines\n";
}
}
}
use SebastianBergmann\Diff\DiffOutputBuilderInterface;
class SlackDiffOutputBuilder implements DiffOutputBuilderInterface {
public function build($diff): string {
return "```diff\n{$diff}```";
}
}
$differ = new Differ(new SlackDiffOutputBuilder());
$expected = json_encode(['key' => 'value'], JSON_PRETTY_PRINT);
$actual = json_encode(['key' => 'updated'], JSON_PRETTY_PRINT);
echo $differ->diff($expected, $actual);
StrictUnifiedDiffOutputBuilder with context lines reduced:
$builder = new StrictUnifiedDiffOutputBuilder(['contextLines' => 2]);
$differ = new Differ($builder);
App\Exceptions\Handler for exception debugging:
report(function () use ($expected, $actual) {
return [
'diff' => (new Differ(new DiffOnlyOutputBuilder))->diff($expected, $actual),
];
});
diff:view command to compare database records:
$model1 = Model::find(1);
$model2 = Model::find(2);
$this->info((new Differ(new StrictUnifiedDiffOutputBuilder))->diff(
$model1->toJson(),
$model2->toJson()
));
assertEquals with custom assertions using diffs:
public function assertJsonDiff(string $expected, string $actual, string $message = ''): void {
$diff = (new Differ(new DiffOnlyOutputBuilder))->diff($expected, $actual);
$this->assertEmpty($diff, $message . "\nDiff:\n{$diff}");
}
Breaking Changes in v9.0.0:
UnifiedDiffOutputBuilder was removed in favor of StrictUnifiedDiffOutputBuilder. Update all usages.$lcs parameter in Differ::diff() was removed. Use the default algorithm (Myers').$differ->diff($old, $new, new TimeEfficientLongestCommonSubsequenceCalculator());
With:
$differ = new Differ(new StrictUnifiedDiffOutputBuilder());
$differ->diff($old, $new);
Line Number Offsets in Chunks:
getStartRange() === 0, getStart() returns the line after which the chunk should be inserted (not the first line of the range).start: 5 and startRange: 0 means the change occurs after line 5.Empty Diffs:
StrictUnifiedDiffOutputBuilder returns an empty string if no differences exist (unlike older versions, which returned headers).$diff = $differ->diff($old, $new);
if (empty($diff)) {
echo "No differences found.\n";
}
Newline Handling:
StrictUnifiedDiffOutputBuilder emits \ No newline at end of file warnings by default. Disable with:
$builder = new StrictUnifiedDiffOutputBuilder([
'emitNoLineEndEofWarning' => false,
]);
Parser Limitations:
Parser only supports unified diff format. Malformed diffs (e.g., from custom tools) may fail.if (strpos($diff, '@@') === false) {
throw new \InvalidArgumentException('Invalid unified diff format');
}
Inspect Diff Objects:
Use print_r or var_export to debug parsed diffs:
$diffObject = $parser->parse($gitDiff);
var_export($diffObject);
Log Raw Diffs: Log the raw diff output before parsing to identify formatting issues:
Log::debug("Raw diff:\n" . $gitDiff);
Context Lines:
Adjust contextLines in StrictUnifiedDiffOutputBuilder to reduce noise in large diffs:
$builder = new StrictUnifiedDiffOutputBuilder(['contextLines' => 1]);
Performance Profiling:
For large diffs (e.g., database dumps), profile the Differ class:
$start = microtime(true);
$diff = $differ->diff($largeString1, $largeString2);
Log::info("Diff time: " . (microtime(true) - $start) . "s");
Custom Output Builders:
Implement DiffOutputBuilderInterface for unique formats:
class HTMLDiffOutputBuilder implements DiffOutputBuilderInterface {
public function build(Diff $diff): string {
return "<pre>" . nl2br(htmlspecialchars($diff->render())) . "</pre>";
}
}
Preprocessing Strings: Normalize strings before diffing (e.g., trim whitespace, sort arrays):
$normalizedOld = preg_replace('/\s+/', ' ', $oldString);
$normalizedNew = preg_replace('/\s+/', ' ', $newString);
$diff = $differ->diff($normalizedOld, $normalizedNew);
Post-Processing Diffs: Filter or highlight diffs using regex:
$diff = $differ->diff($old, $new);
$diff = preg_replace('/^\+\+\+ New$/', '<span class="highlight">+++ New</span>', $diff, 1);
Integration with Laravel: Create a helper trait for diffing:
trait Diffable
How can I help you explore Laravel packages today?