A comprehensive library for generating diff between two strings.
Generated diff can be rendered in all of the standard formats including:
Text renderers:
- Context
- Json (plain text)
- Unified
HTML renderers:
- Combined
- Inline
- Json (HTML)
- Side by Side
Note that for HTML rendered results, you have to add CSS for a better visualization.
You may modify one from example/diff-table.css or write your own from zero.
If you are okay with the default CSS, there is \Jfcherng\Diff\DiffHelper::getStyleSheet()
which can be used to get the content of the example/diff-table.css.
This package is available on Packagist by the name of jfcherng/php-diff.
composer require jfcherng/php-diffSee files and readme in the example/ directory.
<?php
include __DIR__ . '/vendor/autoload.php';
use Jfcherng\Diff\Differ;
use Jfcherng\Diff\DiffHelper;
use Jfcherng\Diff\Factory\RendererFactory;
use Jfcherng\Diff\Options\DifferOptions;
use Jfcherng\Diff\Options\RendererOptions;
use Jfcherng\Diff\Renderer\RendererConstant;
$oldFile = __DIR__ . '/example/old_file.txt';
$newFile = __DIR__ . '/example/new_file.txt';
$old = 'This is the old string.';
$new = 'And this is the new one.';
// renderer class name:
// Text renderers: Context, JsonText, Unified
// HTML renderers: Combined, Inline, JsonHtml, SideBySide
$rendererName = 'Unified';
// the Differ options
$differOptions = new DifferOptions(
// show how many neighbor lines; Differ::CONTEXT_ALL shows the whole file
context: 3,
// ignore case difference
ignoreCase: false,
// ignore line ending difference
ignoreLineEnding: false,
// ignore whitespace difference
ignoreWhitespace: false,
// if the input sequence is too long, give up (especially for char-level diff)
lengthLimit: 2000,
// when inputs are identical, render the whole content rather than an empty result
fullContextIfIdentical: false,
);
// the renderer options
$rendererOptions = new RendererOptions(
// how detailed the rendered HTML in-line diff is? (none, line, word, char)
detailLevel: 'line',
// renderer language: eng, cht, chs, jpn, ...
// or an array which has the same keys with a language file
// check the "Custom Language" section in the readme for more advanced usage
language: 'eng',
// show line numbers in HTML renderers
lineNumbers: true,
// show a separator between different diff hunks in HTML renderers
separateBlock: true,
// show the (table) header
showHeader: true,
// render spaces/tabs as <span class="ch sp"> </span> tags (visualised via CSS)
spaceToHtmlTag: false,
// convert consecutive spaces to in HTML output
spacesToNbsp: false,
// HTML renderer tab width (negative = do not convert into spaces)
tabSize: 4,
// Combined renderer: merge replace-blocks whose changed ratio is at or below this threshold (0–1)
mergeThreshold: 0.8,
// Unified/Context renderers CLI colorization:
// RendererConstant::CLI_COLOR_AUTO = colorize if possible (default)
// RendererConstant::CLI_COLOR_ENABLE = force colorize
// RendererConstant::CLI_COLOR_DISABLE = force no color
cliColorization: RendererConstant::CLI_COLOR_AUTO,
// JSON renderer: emit op tags as human-readable strings instead of ints
outputTagAsString: false,
// JSON renderer: flags passed to json_encode()
// see https://www.php.net/manual/en/function.json-encode.php
jsonEncodeFlags: \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE,
// word-level diff: adjacent segments joined by these characters are merged into one
// e.g. "<del>good</del>-<del>looking</del>" → "<del>good-looking</del>"
wordGlues: ['-', ' '],
// return this string verbatim when the two inputs are identical; null = renderer default
resultForIdenticals: null,
// extra CSS classes added to the diff container <div> in HTML renderers
wrapperClasses: ['diff-wrapper'],
);
// one-line simply compare two files
$result = DiffHelper::calculateFiles($oldFile, $newFile, $rendererName, $differOptions, $rendererOptions);
// one-line simply compare two strings
$result = DiffHelper::calculate($old, $new, $rendererName, $differOptions, $rendererOptions);
// or even shorter if you are happy with default options
$result = DiffHelper::calculate($old, $new, $rendererName);
// custom usage
$differ = new Differ(explode("\n", $old), explode("\n", $new), $differOptions);
$renderer = RendererFactory::make($rendererName, $rendererOptions); // or your own renderer object
$result = $renderer->render($differ);
// use the JSON result to render in HTML
$jsonResult = DiffHelper::calculate($old, $new, 'Json'); // may store the JSON result in your database
$htmlRenderer = RendererFactory::make('Inline', $rendererOptions);
$result = $htmlRenderer->renderArray(json_decode($jsonResult, true));| None-level | Line-level (Default) |
|---|---|
![]() |
![]() |
| Word-level | Char-level |
![]() |
![]() |
<?php $rendererOptions = ['detailLevel' => 'line'];<?php $rendererOptions = ['detailLevel' => 'line'];<?php $rendererOptions = ['detailLevel' => 'word'];This renderer is suitable for articles and always has no line number information.
About the Unified diff format: https://en.wikipedia.org/wiki/Diff#Unified_format
@@ -1,3 +1,4 @@
-<p>Hello World!</p>
+<div>Hello World!</div>
~~~~~~~~~~~~~~~~~~~
+Let's add a new line here.
X
@@ -7,6 +8,5 @@
N
-Do you know in Chinese, "金槍魚罐頭" means tuna can.
+Do you know in Japanese, "魚の缶詰" means fish can.
This is just a useless line.
G
-// remember to delete this line
Say hello to my neighbors.About the Context diff format: https://en.wikipedia.org/wiki/Diff#Context_format
Click to expand
***************
*** 1,3 ****
! <p>Hello World!</p>
~~~~~~~~~~~~~~~~~~~
X
--- 1,4 ----
! <div>Hello World!</div>
~~~~~~~~~~~~~~~~~~~
+ Let's add a new line here.
X
***************
*** 7,12 ****
N
! Do you know in Chinese, "金槍魚罐頭" means tuna can.
This is just a useless line.
G
- // remember to delete this line
Say hello to my neighbors.
--- 8,12 ----
N
! Do you know in Japanese, "魚の缶詰" means fish can.
This is just a useless line.
G
Say hello to my neighbors.This renderer has no detailed diff.
Click to expand
[
[
{
"tag": "rep",
"old": {
"offset": 0,
"lines": ["<p>Hello World! Good-looking.</p>"]
},
"new": {
"offset": 0,
"lines": ["<div>Hello World! Bad-tempered.</div>"]
}
},
{
"tag": "eq",
"old": {
"offset": 1,
"lines": ["~~~~~~~~~~~~~~~~~~~"]
},
"new": {
"offset": 1,
"lines": ["~~~~~~~~~~~~~~~~~~~"]
}
},
{
"tag": "ins",
"old": {
"offset": 2,
"lines": []
},
"new": {
"offset": 2,
"lines": ["Let's add a new line here."]
}
},
{
"tag": "eq",
"old": {
"offset": 2,
"lines": ["X"]
},
"new": {
"offset": 3,
"lines": ["X"]
}
}
],
[
{
"tag": "eq",
"old": {
"offset": 6,
"lines": ["N"]
},
"new": {
"offset": 7,
"lines": ["N"]
}
},
{
"tag": "rep",
"old": {
"offset": 7,
"lines": ["Do you know in Chinese, \"金槍魚罐頭\" means tuna can."]
},
"new": {
"offset": 8,
"lines": ["Do you know in Japanese, \"魚の缶詰\" means fish can."]
}
},
{
"tag": "eq",
"old": {
"offset": 8,
"lines": ["\t \tTab visualization test.", "G"]
},
"new": {
"offset": 9,
"lines": ["\t \tTab visualization test.", "G"]
}
},
{
"tag": "del",
"old": {
"offset": 10,
"lines": ["// remember to delete this line"]
},
"new": {
"offset": 11,
"lines": []
}
},
{
"tag": "eq",
"old": {
"offset": 11,
"lines": ["Say hello to my neighbors."]
},
"new": {
"offset": 11,
"lines": ["Say hello to my neighbors."]
}
}
],
[
{
"tag": "eq",
"old": {
"offset": 14,
"lines": ["B"]
},
"new": {
"offset": 14,
"lines": ["B"]
}
},
{
"tag": "rep",
"old": {
"offset": 15,
"lines": ["Donec rutrum."]
},
"new": {
"offset": 15,
"lines": ["Donec rutrum test.", "There is a new inserted line."]
}
},
{
"tag": "eq",
"old": {
"offset": 16,
"lines": ["C"]
},
"new": {
"offset": 17,
"lines": ["C"]
}
},
{
"tag": "rep",
"old": {
"offset": 17,
"lines": ["Sed dictum lorem ipsum."]
},
"new": {
"offset": 18,
"lines": ["Sed dolor lorem ipsum hendrerit."]
}
},
{
"tag": "eq",
"old": {
"offset": 18,
"lines": [""]
},
"new": {
"offset": 19,
"lines": [""]
}
}
]
]For a "tag": "rep" (8) block, this renderer has HTML-style detailed diff.
If you don't need those detailed diff, consider using the JsonText renderer.
Click to expand
[
[
{
"tag": "rep",
"old": {
"offset": 0,
"lines": ["<<del>p>Hello World! Good-looking.</p</del>>"]
},
"new": {
"offset": 0,
"lines": ["<<ins>div>Hello World! Bad-tempered.</div</ins>>"]
}
},
{
"tag": "eq",
"old": {
"offset": 1,
"lines": ["~~~~~~~~~~~~~~~~~~~"]
},
"new": {
"offset": 1,
"lines": ["~~~~~~~~~~~~~~~~~~~"]
}
},
{
"tag": "ins",
"old": {
"offset": 2,
"lines": [""]
},
"new": {
"offset": 2,
"lines": ["Let's add a new line here."]
}
},
{
"tag": "eq",
"old": {
"offset": 2,
"lines": ["X"]
},
"new": {
"offset": 3,
"lines": ["X"]
}
}
],
[
{
"tag": "eq",
"old": {
"offset": 6,
"lines": ["N"]
},
"new": {
"offset": 7,
"lines": ["N"]
}
},
{
"tag": "rep",
"old": {
"offset": 7,
"lines": ["Do you know in <del>Chinese, \"金槍魚罐頭\" means tuna</del> can."]
},
"new": {
"offset": 8,
"lines": ["Do you know in <ins>Japanese, \"魚の缶詰\" means fish</ins> can."]
}
},
{
"tag": "eq",
"old": {
"offset": 8,
"lines": ["\t \tTab visualization test.", "G"]
},
"new": {
"offset": 9,
"lines": ["\t \tTab visualization test.", "G"]
}
},
{
"tag": "del",
"old": {
"offset": 10,
"lines": ["// remember to delete this line"]
},
"new": {
"offset": 11,
"lines": [""]
}
},
{
"tag": "eq",
"old": {
"offset": 11,
"lines": ["Say hello to my neighbors."]
},
"new": {
"offset": 11,
"lines": ["Say hello to my neighbors."]
}
}
],
[
{
"tag": "eq",
"old": {
"offset": 14,
"lines": ["B"]
},
"new": {
"offset": 14,
"lines": ["B"]
}
},
{
"tag": "rep",
"old": {
"offset": 15,
"lines": ["Donec rutrum."]
},
"new": {
"offset": 15,
"lines": ["Donec rutrum test.", "There is a new inserted line."]
}
},
{
"tag": "eq",
"old": {
"offset": 16,
"lines": ["C"]
},
"new": {
"offset": 17,
"lines": ["C"]
}
},
{
"tag": "rep",
"old": {
"offset": 17,
"lines": ["Sed d<del>ictum lorem ipsum</del>."]
},
"new": {
"offset": 18,
"lines": ["Sed d<ins>olor lorem ipsum hendrerit</ins>."]
}
},
{
"tag": "eq",
"old": {
"offset": 18,
"lines": [""]
},
"new": {
"offset": 19,
"lines": [""]
}
}
]
]If you just want to override some translations of an existing language...
$rendererOptions = [
'language' => [
// use English as the base language
'eng',
// your custom overrides
[
// use "Diff" as the new value of the "differences" key
'differences' => 'Diff',
],
// maybe more overrides if you somehow need them...
],
]This package is built on the top of chrisboulton/php-diff initially. But the original repository looks like no longer maintained. Here have been quite lots of rewrites and new features since then, hence I re-started this as a new package for better visibility.






