Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Diff Laravel Package

sebastian/diff

sebastian/diff is a standalone PHP diff library, factored out of PHPUnit. It generates unified, strict unified, or diff-only output, parses unified diffs into object graphs, and supports custom output builders. Install via Composer as a project or development dependency.

View on GitHub
Deep Wiki
Context7

Getting Started

sebastian/diff is the diff engine PHPUnit uses, factored out as a stand-alone, framework-free component. You use it whenever you need to answer "what changed between string A and string B?" — in test failure output, in an admin config editor, in an audit log, or in a CI validation step.

Install (usually as a dev dependency; PHPUnit already pulls it in transitively, so pin it explicitly if your code calls it directly):

composer require --dev sebastian/diff

First use case — make a failing assertion legible. Take three steps: normalize both sides to a string, build a Differ with an output builder, print the result.

use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;

$differ = new Differ(new StrictUnifiedDiffOutputBuilder([
    'header'    => "--- expected\n+++ actual\n",
    'context'   => 3,
]));

$expected = json_encode($expected, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
$actual   = json_encode($actual,   JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);

if ($expected !== $actual) {
    $this->fail("API payload mismatch:\n" . $differ->diff($expected, $actual));
}

Where to look first in the source tree (it is small and readable — five directories):

  1. src/Differ.php — the entry point; diff() returns a string, diffToArray() returns a structured array of ['type' => int, 'content' => string].
  2. src/Output/ — the three builders (StrictUnifiedDiffOutputBuilder, DiffOnlyOutputBuilder, and whatever remains of UnifiedDiffOutputBuilder depending on your version) plus DiffOutputBuilderInterface.
  3. src/Parser.php, src/Diff.php, src/Chunk.php, src/Line.php — the object graph you get back when you consume a diff instead of producing one.
  4. src/Line.php constants — ADDED (1), REMOVED (2), UNCHANGED (3). The parser hands you these integers, not strings, so switch on the constants, never on magic numbers.

Choose your builder by consumer, not by taste: StrictUnifiedDiffOutputBuilder when the output must be patch/git apply compatible or must be parseable later; DiffOnlyOutputBuilder when you only want the changed lines (faster to render, smaller payloads); UnifiedDiffOutputBuilder only if you are on an <9.0 constraint. If none fit, implement DiffOutputBuilderInterface — that is the main extension point.

Implementation Patterns

1. Test-failure diffs with a shared helper. Wrap the differ once so every assertion in your suite renders changes identically. Keep the builder stateless and reusable:

// app/Support/Diff.php
final class TextDiff
{
    private static ?Differ $differ = null;

    public static function between(string $expected, string $actual): string
    {
        return (self::$differ ??= new Differ(
            new StrictUnifiedDiffOutputBuilder([
                'header'                 => "--- expected\n+++ actual\n",
                'context'                => 3,
                'addLineNumbers'        => false,
                'emitNoLineEndEofWarning' => false, // in-memory strings have no EOF
            ])
        ))->diff($expected, $actual);
    }
}

emitNoLineEndEofWarning => false matters: for string comparison in a test report the \ No newline at end of file marker is noise, and disabling it is exactly the use case it was added for.

2. Compare normalized payloads, not raw ones. Diffs over minified JSON are unreadable and slow. json_encode(..., JSON_PRETTY_PRINT) (and ksort() for key-order noise) both as inputs, or better, diffToArray() the decoded arrays and diff the re-encoded canonical form. This turns "1 line changed" into a precise, reviewable hunk.

3. Admin config editor / audit trail. Use StrictUnifiedDiffOutputBuilder so the stored diff is a real unified diff — Git-compatible, greppable, and re-parseable months later. Store it alongside the record version rather than storing two full blobs:

$diff = (new Differ(new StrictUnifiedDiffOutputBuilder([
    'header' => "--- {$old->version}\n+++ {$new->version}\n",
    'context' => 5,
])))->diff($old->payload, $new->payload);

AuditEntry::create(['entity' => $model->getKey(), 'patch' => $diff]);

4. Parse diffs into objects for compliance/reporting. Never regex a unified diff yourself — Parser::parse() gives you Diff[] → Chunk[] → Line[] with from/to paths, hunk ranges, and typed lines:

foreach ((new Parser)->parse($storedPatch) as $file) {
    foreach ($file->getChunks() as $chunk) {
        foreach ($chunk->getLines() as $line) {
            if ($line->getType() === Line::REMOVED && str_contains($line->getContent(), 'ssn')) {
                // flag a sensitive field being dropped
            }
        }
    }
}

5. Custom rendering for humans. Implement DiffOutputBuilderInterface to emit HTML, Markdown, or a JSON structure with line numbers for a side-by-side viewer. The differ hands your build() method the internal array of typed lines, so colorization is a match on Line::ADDED/REMOVED/UNCHANGED. Keep the builder free of per-call state; one instance may be reused across many diff() calls.

6. Get git diffs without a git library. The README's parse example imports SebastianBergmann\Git, which is not a dependency of this package. Shell out and feed the result in:

$patch = shell_exec('git -C ' . escapeshellarg($repo) . ' diff ' . escapeshellarg("$a..$b"));
$parsed = (new Parser)->parse((string) $patch);

Use symfony/process instead of shell_exec in anything long-lived, and check the exit code before parsing.

7. Feed it your own data. Differ::diffToArray() is the API for programmatic work — it saves you from parsing your own generated string. Reach for diff() only at the presentation boundary.

Gotchas and Tips

The README is out of sync with 9.x. As of 9.0.0, UnifiedDiffOutputBuilder and AbstractChunkOutputBuilder were removed — the README's first example, and most StackOverflow answers, will fatal with Class not found. On 9.x use StrictUnifiedDiffOutputBuilder; if you must keep UnifiedDiffOutputBuilder, pin ^8.3. Verify with composer show sebastian/diff before copying any snippet.

Constructor signatures drift across 8.x. UnifiedDiffOutputBuilder gained $contextLines (8.1.0) and $emitNoLineEndEofWarning (8.3.0); StrictUnifiedDiffOutputBuilder moved toward an options array (header, context, addLineNumbers, emitDiffLineEndWarning, emitNoLineEndEofWarning); 8.2.0 changed unified-builder behavior and 8.2.1 reverted it. Prefer new StrictUnifiedDiffOutputBuilder([...]) with named options and inspect the constructor on the version you actually have.

9.0.0 removed the $lcs parameter and the LCS interfaces. Passing a third argument to diff()/diffToArray() no longer selects a calculator — extra arguments to userland methods are silently ignored in PHP, so you get no error, just Myers' linear-space algorithm every time (a memory win, but a silent behavior change if you were benchmarking). Delete those arguments before upgrading.

8.3.0 changed two output defaults. No difference now yields an empty string instead of a header-only diff, and writeHunk() emits the literal \ No newline at end of file text. If you assert on exact diff strings or pipe output to patch, re-baseline your fixtures on upgrade.

Pin >= 9.0.1 if you parse arbitrary text. 9.0.1 fixed "Parser drops lines that look like file headers" — a real hazard when diffing YAML front matter, Markdown, SQL, or any content containing --- /+++ at line start.

Zero-length hunks break naive line math. When getStartRange() / getEndRange() return 0, getStart() / getEnd() are the line after which content is inserted or deleted — one lower than the "first line of the range". Add 1 when mapping to a gutter, or your side-by-side viewer will be off by one on pure insertions and deletions.

Line endings and trailing newlines dominate the diff. The differ splits on \n; a CRLF file compared to an LF file shows every line as changed. Normalize with str_replace("\r\n", "\n", $s) before diffing, and be aware that a missing final newline is itself a difference (that's what the EOF marker is about).

Text only, whole-string only. No binary diffs, no streaming, no incremental updates. Large generated files (lockfiles, minified bundles, DB dumps) will be slow and produce unreadable output — strip or skip them first. Common-prefix/suffix trimming happens internally, so diffs of mostly-identical large files are fine; diffs of two unrelated large files are not.

Wrap the dependency. PHPUnit's own composer.json constrains this package. If your app depends on it directly, declare it in require-dev with an explicit constraint so a PHPUnit bump can't silently change your diff output. Even better, hide it behind the TextDiff helper above so a future 10.0 removal costs you one file.

Custom builders: be defensive. Unchanged/added/removed are not the only things a builder can receive in every version, and 8.3.0 made the stock builder silently skip unknown line types. Mirror that behavior — skip rather than throw — or a single unexpected type will take down a whole test report.

Debugging tip: when output looks wrong, dump $differ->diffToArray($a, $b) instead of the string. You immediately see whether the problem is in the diff computation (wrong types) or in the builder (right types, wrong rendering).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console