sebastian/diff
Standalone PHP diff library extracted from PHPUnit. Generate unified/strict unified diffs or diff-only output between strings via Differ and output builders, or parse unified diff text into an object graph with Parser for further processing.
Installation:
composer require sebastian/diff
For development-only use (e.g., testing):
composer require --dev sebastian/diff
First Use Case: Generate a diff between two strings in a Laravel test or command:
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
$differ = new Differ(new UnifiedDiffOutputBuilder);
$diff = $differ->diff('Expected output', 'Actual output');
Where to Look First:
Differ class: Core diff generation.UnifiedDiffOutputBuilder, StrictUnifiedDiffOutputBuilder, or DiffOnlyOutputBuilder for different formats.Parser class: For parsing diff strings into structured objects (useful for Git diffs or custom diff processing).Replace generic assertion failures with human-readable diffs in Laravel’s phpunit or pestphp:
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
public function testStringComparison()
{
$expected = "Hello, world!";
$actual = "Hello, Laravel!";
$differ = new Differ(new UnifiedDiffOutputBuilder);
$diff = $differ->diff($expected, $actual);
$this->fail("Strings differ:\n{$diff}");
}
Adjust context lines (new in v8.1.0) for readability:
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
$builder = new UnifiedDiffOutputBuilder(3); // 3 lines of context
$differ = new Differ($builder);
Create a custom output builder for JSON diffs:
use SebastianBergmann\Diff\DiffOutputBuilderInterface;
use SebastianBergmann\Diff\Line;
class JsonDiffOutputBuilder implements DiffOutputBuilderInterface
{
public function build(array $diffs): string
{
return json_encode(array_map(function ($diff) {
return [
'from' => $diff->getFrom(),
'to' => $diff->getTo(),
'chunks' => array_map(function ($chunk) {
return [
'start' => $chunk->getStart(),
'lines' => array_map(function ($line) {
return [
'type' => $line->getType(),
'content' => $line->getContent(),
];
}, $chunk->getLines()),
];
}, $diff->getChunks()),
];
}, $diffs));
}
}
Integrate with Git for version control workflows:
use SebastianBergmann\Diff\Parser;
use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\ProcessFailedException;
$parser = new Parser();
$process = new Process(['git', 'diff', 'HEAD~1', 'HEAD']);
$process->run();
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
$diffObject = $parser->parse($process->getOutput());
Use diffs in Laravel’s Log facade or laravel-debugbar:
use Illuminate\Support\Facades\Log;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;
$differ = new Differ(new DiffOnlyOutputBuilder);
$diff = $differ->diff($expectedConfig, $actualConfig);
Log::error("Configuration mismatch:\n{$diff}");
Extend phpunit or pestphp assertions:
// In a custom assertion helper (e.g., `app/Helpers/DiffHelper.php`)
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;
function assertDiff($expected, $actual, $message = '')
{
$differ = new Differ(new UnifiedDiffOutputBuilder);
$diff = $differ->diff($expected, $actual);
$this->fail("{$message}\nDiff:\n{$diff}");
}
Leverage Laravel Service Providers:
Bind the Differ or Parser to the container for dependency injection:
// In a service provider (e.g., `AppServiceProvider`)
$this->app->singleton(Differ::class, function ($app) {
return new Differ(new UnifiedDiffOutputBuilder);
});
Use in Artisan Commands: Generate diffs for CLI tools:
use Illuminate\Console\Command;
use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;
class CompareCommand extends Command
{
protected $signature = 'diff:compare {file1} {file2}';
protected $description = 'Compare two files and show the diff';
public function handle(Differ $differ)
{
$file1 = file_get_contents($this->argument('file1'));
$file2 = file_get_contents($this->argument('file2'));
$diff = $differ->diff($file1, $file2);
$this->output->write($diff);
}
}
Combine with Laravel Filesystem: Diff file contents dynamically:
use Illuminate\Support\Facades\Storage;
use SebastianBergmann\Diff\Differ;
$file1 = Storage::disk('local')->get('file1.txt');
$file2 = Storage::disk('local')->get('file2.txt');
$diff = app(Differ::class)->diff($file1, $file2);
Extend for Custom Use Cases:
PHP Version Compatibility:
Line Endings:
\n vs. \r\n). Normalize strings before comparing:
$string1 = str_replace(["\r\n", "\r"], "\n", $string1);
$string2 = str_replace(["\r\n", "\r"], "\n", $string2);
Large Files:
DiffOnlyOutputBuilder to reduce output size.Zero-Length Chunks:
getStartRange() or getEndRange() returns 0, the line numbers are offset by 1. Account for this in custom parsers:
$startLine = $chunk->getStart() - 1; // Adjust for zero-length chunks
Whitespace Sensitivity:
trim() or regex to ignore whitespace if needed:
$normalizedString1 = preg_replace('/\s+/', ' ', $string1);
$normalizedString2 = preg_replace('/\s+/', ' ', $string2);
Output Builder Quirks:
StrictUnifiedDiffOutputBuilder is more strict than UnifiedDiffOutputBuilder and may produce different output for edge cases (e.g., empty files).DiffOnlyOutputBuilder omits context lines entirely, which may make diffs harder to read for large changes.Inspect Diff Objects:
Use print_r or var_dump to debug parsed diffs:
$parser = new Parser();
$diffObject = $parser->parse($diffString);
print_r($diffObject);
Log Raw Diffs: Log the raw diff string before processing to verify input:
Log::debug("Raw diff input:\n{$diffString}");
Handle Encoding Issues: Ensure strings are in the same encoding (e.g., UTF-8) before diffing:
$string1 = mb_convert_encoding
How can I help you explore Laravel packages today?