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('original content', 'modified content');
Where to Look First:
Differ class: Core diff computation.StrictUnifiedDiffOutputBuilder (recommended), DiffOnlyOutputBuilder (minimal output).Parser class: For parsing existing diffs (e.g., from Git) into structured objects.Generating Diffs:
$differ = new Differ(new StrictUnifiedDiffOutputBuilder(['header' => '--- Old ---', '--- New ---']));
$diff = $differ->diff($oldString, $newString);
$differ = new Differ(new DiffOnlyOutputBuilder);
$diff = $differ->diff($oldString, $newString);
Parsing Diffs:
use SebastianBergmann\Diff\Parser;
$parser = new Parser;
$diffObject = $parser->parse($gitDiffString);
foreach ($diffObject->getChunks() as $chunk) {
foreach ($chunk->getLines() as $line) {
echo $line->getContent(); // Line content
echo $line->getType(); // 1=added, 2=removed, 3=context
}
}
Custom Output:
DiffOutputBuilderInterface for bespoke formatting:
class CustomOutputBuilder implements DiffOutputBuilderInterface {
public function build($diff): string {
return "Custom: " . $diff->getLinesAsString();
}
}
$differ = new Differ(new CustomOutputBuilder);
Integration with Laravel:
use Illuminate\Support\Facades\Log;
Log::debug('Config diff:', ['diff' => $differ->diff(config('old'), config('new'))]);
$this->assertStringContainsString('+added line', $differ->diff($expected, $actual));
$responseDiff = $differ->diff($expectedJson, $response->getContent());
$this->assertEmpty($responseDiff); // Assert no differences
Performance-Critical Scenarios:
StrictUnifiedDiffOutputBuilder with optimized options:
$builder = new StrictUnifiedDiffOutputBuilder([
'contextLines' => 3, // Reduce context for large files
'addLineNumbers' => false,
]);
Breaking Changes in v9.0.0:
UnifiedDiffOutputBuilder and AbstractChunkOutputBuilder were removed. Use StrictUnifiedDiffOutputBuilder instead.LongestCommonSubsequenceCalculator interface and $lcs parameter are deprecated (removed in v9.0.0). No replacement needed—Myers' algorithm is now the default.Line Number Offsets:
getStartRange() === 0 or getEndRange() === 0 use 1-based indexing after insertion/deletion. Example:
$chunk->getStart(); // Returns line *after* which to insert (not the first line of the chunk).
Empty Diffs:
UnifiedDiffOutputBuilder returns an empty string if no differences exist (previously returned headers). Handle this in assertions:
$diff = $differ->diff($a, $b);
$this->assertEmpty($diff); // Fails if headers are expected.
Newline Warnings:
emitNoLineEndEofWarning (default: true) adds \ No newline at end of file warnings. Disable for test comparisons:
$builder = new StrictUnifiedDiffOutputBuilder(['emitNoLineEndEofWarning' => false]);
PHP 8.3+ Compatibility:
Inspect Diff Objects:
print_r($parser->parse($diffString)) to debug parsed diffs. Key properties:
Diff::getChunks(): Array of Chunk objects.Chunk::getLines(): Array of Line objects with type (1=added, 2=removed, 3=context) and content.Handle Binary Data:
spatie/array-to-xml or base64-encode first.Custom Formatting Quirks:
DiffOutputBuilderInterface, ensure your build() method handles:
"").$diff object structure).Performance Tuning:
contextLines in StrictUnifiedDiffOutputBuilder:
$builder = new StrictUnifiedDiffOutputBuilder(['contextLines' => 1]);
Laravel-Specific:
Differ and Parser per request/artisan command to prevent memory leaks.$diffCache = Cache::remember("diff_{$key}", now()->addHours(1), function () use ($old, $new) {
return $differ->diff($old, $new);
});
Custom Diff Algorithms:
Differ by injecting a custom DiffAlgorithm (internal interface). Example:
class CustomAlgorithm implements \SebastianBergmann\Diff\DiffAlgorithm {
public function compute($expected, $actual): \SebastianBergmann\Diff\Diff {
// Implement custom logic (e.g., semantic diff for JSON).
}
}
$differ = new Differ(new CustomAlgorithm(), new StrictUnifiedDiffOutputBuilder);
Output Builder Extensions:
StrictUnifiedDiffOutputBuilder to modify behavior:
class ColoredDiffOutputBuilder extends StrictUnifiedDiffOutputBuilder {
protected function writeLine(string $line, int $type): void {
$color = $type === 1 ? "\033[32m" : "\033[31m"; // Green/red
echo $color . $line . "\033[0m";
}
}
Parser Extensions:
Parser to handle custom diff formats:
class CustomParser extends Parser {
protected function parseChunk(string $chunk): \SebastianBergmann\Diff\Chunk {
// Custom parsing logic.
}
}
Laravel Service Provider:
// config/app.php
'aliases' => [
'Diff' => SebastianBergmann\Diff\Facades\Diff::class,
],
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(Differ::class, function () {
return new Differ(new StrictUnifiedDiffOutputBuilder);
});
}
How can I help you explore Laravel packages today?