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

Finediff Laravel Package

d4h/finediff

A PHP implementation of the FineDiff algorithm for generating compact, granular diffs between strings. It supports character, word, and line-level comparisons and can render HTML or plain-text output, making it useful for version tracking, wikis, and collaborative editing.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer and pin the version — the package is low-traffic, so treat upgrades as reviewed changes, not blind composer updates:

composer require d4h/finediff

The first thing to do is read src/Diff.php and src/Granularity/. Everything else (renderers, opcodes) hangs off those two. The public surface is small: a Diff object, a granularity strategy, and a renderer.

Minimal first use case — showing what changed between two versions of a user-editable field:

use FineDiff\Diff;
use FineDiff\Granularity\Word;

$diff = new Diff(new Word());
echo $diff->render($oldBody, $newBody);
// -> "The quick <del>brown</del><ins>red</ins> fox"

That one call is the 90% case. render() returns HTML with <ins>/<del> wrappers; if you need plain text, pass new FineDiff\Render\Text() as the second constructor argument instead.

If you're only after whether something changed, don't reach for this package — $a !== $b is cheaper. FineDiff is for presenting what changed.

Implementation Patterns

Pick granularity deliberately. This is the main design decision and it changes output quality more than anything else:

use FineDiff\Diff;
use FineDiff\Granularity\{Character, Word, Sentence, Paragraph};

new Diff(new Character());  // intra-word, noisy for prose
new Diff(new Word());       // best default for prose and UI previews
new Diff(new Sentence());   // good for changelogs / "what changed in this doc"
new Diff(new Paragraph());  // good for article/comment revision summaries

Use Character only for short strings (titles, slugs, tags) where word boundaries aren't meaningful. For long-form content it produces unreadable soup.

Process once, render many. process() computes the opcode string; render() re-runs the granularity split but reuses the opcodes. If you're rendering the same diff in two formats (HTML preview + plaintext email), split the work:

$diff = new Diff(new Word());
$opcodes = $diff->process($old, $new);

$htmlDiff = new Diff(new Word(), new \FineDiff\Render\Html());
$htmlDiff->setOpcodes($opcodes); // or re-process; cheap for moderate sizes

$textDiff = new Diff(new Word(), new \FineDiff\Render\Text());

Reusing the opcode string is also how you persist diffs cheaply — store the opcodes (and the original text) rather than a rendered HTML blob. Rendering is deterministic, so you can regenerate at display time and restyle later.

Wrap it in a small service, not call sites. Diff rendering is presentation logic that tends to grow (escaping, CSS classes, truncation on long text). A single DiffPresenter or VersionDiff class in your app gives you one place to fix escaping bugs:

final class VersionDiff
{
    public function html(string $old, string $new): string
    {
        $diff = new Diff(new Word());
        return $diff->render($old, $new);
    }
}

Integrate at the edges. Typical Laravel wiring: a Blade component or a @renderDiff directive for audit logs and revision previews; a queued job for batch-generating opcodes when a document is saved (so the request cycle stays cheap); a Markdown/email-safe plaintext renderer for notifications.

Custom renderers are the extension point. Extend the base renderer and override the insert/delete output to emit class="diff-ins", <mark>, or whatever your CSS expects. This is preferable to regex-patching the default <ins>/<del> output — the default markup is intentionally minimal and not configurable via options.

Gotchas and Tips

Output is not escaped. The default HTML renderer inserts your raw content between <ins> and <del>. If either side contains user input (it usually does), you must escape both strings before diffing, or the diff will happily emit stored XSS. Escape first, diff second — escaping after would mangle the tags.

Multibyte and Unicode are a real risk. Granularity splitting and opcode offsets are byte-oriented. Non-ASCII content (emoji, CJK, accented characters) can produce broken output or garbled insertions. Test explicitly with your actual content — if it's user-facing and international, budget time for this or normalize before diffing.

Large inputs get expensive. Character granularity on a multi-KB string is the worst case; the algorithm is quadratic-ish in the pathological case. If you're diffing whole documents, use Sentence or Paragraph, cap input length, or do it in a queued job. Diffing 100KB of prose character-by-character can genuinely time out a request.

Whitespace-only changes are invisible noise. Trailing spaces, \r\n vs \n, and non-breaking spaces produce diffs that look empty to users but light up the UI. Normalize line endings and trim before diffing if the semantics allow it.

Empty and identical inputs. $old === $new should short-circuit before constructing a Diff — skip the work and render the original string. Empty-string-to-content is safe but produce a bare <ins> block; make sure your CSS handles that.

The opcode string is a plain format, not magic. It's a compact run-length sequence (c copy, d delete, i insert, each with a count). Useful to know when debugging "why did it highlight that?" — dump the opcodes and match them against your inputs. Don't hand-parse it unless you have to; stick to the renderers.

Version pinning and bus factor. With ~14 stars and no dependents, this is a small, quiet dependency. Composer-lock it, keep a test that exercises your exact rendering path, and be prepared to fork or vendor it. It's MIT and small enough that a fork is a realistic fallback — the algorithm is the value, not the integration surface.

Test with fixtures, not assertions on exact markup. Asserting on full HTML output makes you brittle to whitespace and markup tweaks. Assert on the semantic content — that the deleted substring is inside a <del> and the inserted one inside <ins> — or just snapshot the output and review snapshot diffs on upgrade.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib