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:^9.0
For development-only use (e.g., testing):
composer require --dev sebastian/diff:^9.0
First Use Case: Generate a unified diff between two strings in a Laravel controller or service:
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;
$differ = new Differ();
$diff = $differ->diff('old string', 'new string');
return response()->json(['diff' => $diff]);
Where to Look First:
Differ class: Core diff logic (now uses Myers' linear-space algorithm by default).StrictUnifiedDiffOutputBuilder: Replaces UnifiedDiffOutputBuilder and AbstractChunkOutputBuilder.Parser: For parsing existing diffs into structured objects (e.g., for version control tools).Generating Diffs in Tests:
Use StrictUnifiedDiffOutputBuilder for PHPUnit-style diffs in custom assertions:
$differ = new Differ();
$diff = $differ->diff($expectedJson, $actualJson);
$this->assertEmpty($diff); // Passes if no differences
Custom Output Formatting:
Extend DiffOutputBuilderInterface for bespoke formats (e.g., HTML or Markdown):
use SebastianBergmann\Diff\Output\DiffOutputBuilderInterface;
class CustomDiffOutputBuilder implements DiffOutputBuilderInterface {
public function build(array $diff): string {
return "<pre>" . htmlspecialchars(implode("\n", $diff)) . "</pre>";
}
}
Parsing Git Diffs: Integrate with Git commands to compare file versions:
use SebastianBergmann\Diff\Parser;
use Symfony\Component\Process\Process;
$process = new Process(['git', 'diff', 'HEAD~1', 'HEAD']);
$process->run();
$parser = new Parser();
$diff = $parser->parse($process->getOutput());
API Response Comparison: Suppress EOF warnings for JSON/XML diffs:
$outputBuilder = new StrictUnifiedDiffOutputBuilder(
$differ->diff($expected, $actual),
false, // addLineNumbers
false, // emitDiffLineEndWarning
false // emitNoLineEndEofWarning (suppresses EOF noise)
);
Debugging Workflow:
Use DiffOnlyOutputBuilder (if available in future versions) or parse diffs for isolated changes:
$diffArray = $differ->diffToArray($old, $new);
foreach ($diffArray as $chunk) {
if ($chunk->isUnchanged()) continue;
$this->log->debug($chunk->getLines());
}
CI/CD Pipeline: Parse diffs to validate deployments or migrations:
$parser = new Parser();
$diff = $parser->parse(file_get_contents('migration_diff.patch'));
if ($diff->hasChanges()) {
throw new \RuntimeException("Unapproved changes detected!");
}
Version Control Hooks: Validate commit messages or diffs pre-push:
$diff = $differ->diff($oldFile, $newFile);
if (str_contains($diff, '@@')) {
// Trigger custom validation logic
}
Laravel Service Providers:
Bind the Differ to the container for dependency injection:
$this->app->singleton(Differ::class, function ($app) {
return new Differ();
});
Artisan Commands: Create a custom command for ad-hoc diffs:
use Illuminate\Console\Command;
use SebastianBergmann\Diff\Differ;
class DiffCommand extends Command {
protected $signature = 'diff:compare {path1} {path2}';
protected $description = 'Compare two files';
public function handle(Differ $differ) {
$content1 = file_get_contents($this->argument('path1'));
$content2 = file_get_contents($this->argument('path2'));
$this->output->write($differ->diff($content1, $content2));
}
}
Middleware for API Diffs: Log diffs between request/response payloads:
public function handle($request, Closure $next) {
$response = $next($request);
if ($request->is('api/*')) {
$diff = $this->app->make(Differ::class)
->diff($request->getContent(), $response->getContent());
\Log::debug('API Diff:', ['diff' => $diff]);
}
return $response;
}
Removed UnifiedDiffOutputBuilder:
Replace all instances of UnifiedDiffOutputBuilder with StrictUnifiedDiffOutputBuilder. This is a breaking change in v9.0.0.
Removed LCS Calculator Interface:
The $lcs parameter in Differ::diff() and Differ::diffToArray() is no longer supported. The package now uses Myers' linear-space algorithm by default.
Empty Diff Handling:
StrictUnifiedDiffOutputBuilder returns an empty string for no differences (no headers). Adjust assertions or output logic if relying on headers:
// Old (pre-9.0.0):
$this->assertStringContainsString('--- Original', $diff);
// New (9.0.0+):
$this->assertEmpty($diff); // For identical inputs
Line Number Offsets:
Chunks with getStartRange() or getEndRange() returning 0 use 1-based indexing after the chunk. Example:
$chunk->getStart() === 87 // Refers to line *after* the chunk (insertion point)
Strict Mode Quirks:
StrictUnifiedDiffOutputBuilder skips diff entries with unknown types silently. Validate input data if using custom diff formats.
Parser Debugging:
Use print_r() or var_dump() on parsed diff objects to inspect structure:
$parsedDiff = $parser->parse($diffString);
print_r($parsedDiff->getChunks()[0]->getLines());
Output Builder Issues: If diffs appear malformed, check:
\n (Unix-style).addLineNumbers, emitDiffLineEndWarning, etc.Performance:
For large diffs (e.g., database dumps), stream output or use diffToArray() to reduce memory usage.
Header Customization:
StrictUnifiedDiffOutputBuilder supports a header option for custom headers:
$outputBuilder = new StrictUnifiedDiffOutputBuilder(
$differ->diff($old, $new),
false, // addLineNumbers
false, // emitDiffLineEndWarning
false, // emitNoLineEndEofWarning
"Custom Header: $file1 vs $file2" // header
);
Context Lines:
Adjust context lines in StrictUnifiedDiffOutputBuilder for broader/narrower diffs:
$outputBuilder = new StrictUnifiedDiffOutputBuilder(3); // 3 lines of context
PHP Compatibility: Ensure your Laravel app targets PHP 8.1+ for full compatibility with v9.0.0.
Custom Diff Algorithms:
Override SebastianBergmann\Diff\Differ to implement custom diff logic (e.g., semantic diffs for code).
Output Formatters:
Implement DiffOutputBuilderInterface for formats like:
<span class="added">/<span class="removed">.+++/--- syntax for GitHub-flavored diffs.Integration with Laravel Filesystem: Diff file contents directly from storage:
use Illuminate\Support\Facades\Storage;
$oldContent = Storage::disk('local')->get('old_file.txt');
$newContent = Storage::disk('local')->get('new_file.txt');
$diff = $differ->diff($oldContent, $newContent);
Event Listeners: Trigger events on diff changes (e
How can I help you explore Laravel packages today?