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.
Architecture fit
sebastian/diff is a zero-dependency, framework-agnostic library. It ships no service provider, facade, config file, or Laravel-facing API surface — it is a pure value-in/text-out component with two entry points (Differ, Parser) and a pluggable DiffOutputBuilderInterface. That makes it a clean fit for Laravel applications: it can be consumed anywhere (controllers, queued jobs, Artisan commands, console test reporters) without touching the framework container.Differ + an output builder → text, or diff text → Parser → Diff/Chunk/Line object graph. This is the right shape for our likely use cases (test-failure reporting, config/audit change rendering, structured consumption of Git diffs), all of which are batch, not streaming.Integration feasibility
composer require sebastian/diff), trivial to call, ~20 lines to wire into an exception handler, a validation message builder, or a queued audit job. No infrastructure, migrations, or config.require entry plus a constraint that does not collide with the PHPUnit-driven one.Technical risk
UnifiedDiffOutputBuilder, AbstractChunkOutputBuilder, the LCS interfaces and the $lcs parameter). Adopting this in production code means accepting recurring, sometimes breaking upgrades on the maintainer's schedule, not ours.composer.json platform constraints before commitment — not assumed from the changelog wording.UnifiedDiffOutputBuilder behavior to align with the strict builder, and 8.2.1 reverted it. 8.3.0 changed no-diff output from "header" to empty string and altered \ No newline at end of file emission. Any golden-file or snapshot test we build on this library's exact output is a version-coupled assertion.Parser diffs from external sources (Git output, vendor patches, user uploads), we are trusting a parser with a recent silent-mis-parse history. Treat parsed output as advisory until validated.Key questions
diff/git diff, or reimplements LCS, and what does that cost us today?StrictUnifiedDiffOutputBuilder) or only human-readable presentation?Stack fit
App\Support\Diff\DiffRenderer implementing a small DiffRendererInterface — bound in a service provider, so the vendor class never leaks into controllers, Blade templates, or domain code. This keeps the blast radius of a breaking major to one file.StrictUnifiedDiffOutputBuilder for anything a human will apply or paste into a patch/git flow; use DiffOnlyOutputBuilder for high-volume, presentation-only contexts (CI failure summaries, log/JSON payload diffs) where context lines are noise and cost.Parser behind a separate adapter and feature-flag it. Do not let structured-diff objects become a load-bearing internal data model — the object graph is the vendor's, not ours.Stringable and as a queued job for anything above a small input threshold.Migration path
grep for shell_exec/exec/proc_open with diff/git diff, Symfony\Component\Process diff calls, and any homegrown LCS/similar_text/array_diff-based comparators.require-dev via PHPUnit) to explicit require with a caret constraint, and confirm the resolved version is identical to the one PHPUnit pulls — one version in the lockfile, not two.Parser adoption last, and only for trusted, well-formed inputs initially.Compatibility
^9.0 and treat the major as the compatibility boundary — do not float across majors automatically in composer update, and pin in composer.json with an explicit comment/ADR reference so a future maintainer understands why.UnifiedDiffOutputBuilder removal in 9.0: any snippet copied from older blog posts, Stack Overflow, or this README's own earlier examples will fail on 9.x. The README we have still documents UnifiedDiffOutputBuilder in its primary usage example with no removal notice — expect engineers to follow it and hit a class-not-found. The adapter plus a short internal note is the mitigation.sebastian/diff major bump.Sequencing
StrictUnifiedDiffOutputBuilder. Add a characterization test.DiffOutputBuilderInterface implementations for our own presentation formats (Markdown/HTML docs, email digests) — this is where the abstraction earns its keep, and it is genuinely low-risk because we own the class.Maintenance
Differ::diff, diffToArray, Parser::parse, three output builders) is narrow.Support
UnifiedDiffOutputBuilder no longer exists in 9.x, and (b) the documented quirk that a zero-length chunk range returns a line number one lower than intuition suggests, which affects insertion-point logic in custom renderers. Put both in the adapter's docblock.\ No newline at end of file marker and no-difference output (empty string since 8.3) are the other two recurring "why does our diff look wrong" tickets.Scaling
DiffOnlyOutputBuilder is the lever for high-throughput paths; StrictUnifiedDiffOutputBuilder with reduced context lines is the lever when full unified output is required.Failure modes
Parser bug class fixed in 9.0.1 (mis-dropping header-like lines). If parsed diffs drive compliance or audit decisions, a silent wrong answer is worse than an error. Mitigate with input sanity checks, round-trip assertions on a sample, and treating parser output as non-authoritative for anything audited.Differ directly from a controller, and a future major turns a one-file fix into an application-wide sweep. Enforce the adapter via code review, or a static-analysis rule / architecture test.Ramp-up
How can I help you explore Laravel packages today?