Diffing
Diff::compare(string $old, string $new): DiffResult compares two text strings line by line. The default Myers engine returns only changed regions and three surrounding context lines.
Options
All options are immutable and can be combined:
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
$diff = Diff::build()
->withWordDiff()
->ignoreWhitespace()
->contextLines(5)
->maxBytes(10_000_000);
$result = $diff->compare(
"The quick brown fox\n",
"The fast brown fox\n",
);
echo count($result->hunks())." changed region(s)\n";| Method | Default | Effect |
|---|---|---|
withWordDiff(bool $on = true) |
false |
Adds word spans to paired changed lines. |
ignoreWhitespace(bool $on = true) |
false |
Compares trimmed lines after collapsing whitespace runs. Original text is retained in the result. |
contextLines(int $n) |
3 |
Sets unchanged lines around each change. The value cannot be negative. |
maxBytes(int $bytes) |
5_000_000 |
Limits each input string. The value must be positive. |
withEngine(DiffEngineInterface $engine) |
Myers | Selects another comparison engine. |
compare() throws SizeLimitException when either input exceeds the configured
limit and BinaryInputException when an input appears to contain binary data.
Binary detection checks null bytes and excessive control characters near the
beginning of the input. See Errors before retrying a rejected input.
Inspect the result
DiffResult::isEmpty() reports whether the inputs differ. hunks() returns the changed regions, while the newline flags preserve whether each input ended with a line break.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
$result = Diff::build()
->withWordDiff()
->compare("The quick fox\n", "The fast fox\n");
foreach ($result->hunks() as $hunk) {
printf(
"old %d,%d; new %d,%d\n",
$hunk->oldStart,
$hunk->oldLen,
$hunk->newStart,
$hunk->newLen,
);
foreach ($hunk->edits as $edit) {
printf("%s %s\n", $edit->op, $edit->text);
foreach ($edit->wordSpans as $span) {
printf(" %s %s\n", $span->op, $span->text);
}
}
}The result model contains:
DiffResult: hunks, optional labels, and trailing-newline flags.Hunk: old and new ranges plus its edits.Edit: anadd,del, oreqoperation and its original text.WordSpan: anadd,del, oreqtoken produced by word-level comparison.
Use Rendering when you need formatted output.
Engines
The default MyersDiffEngine computes a minimal edit script in O(ND) time and
is suitable for general use. LcsDiffEngine uses the classic longest common
subsequence algorithm. Its O(MN) time and memory cost makes it appropriate
only for small, controlled inputs.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Engine\LcsDiffEngine;
$result = Diff::build()
->withEngine(new LcsDiffEngine())
->compare("A\nB\n", "A\nC\n");
echo count($result->hunks())." changed region(s)\n";Both built-in engines enforce the configured size limit, reject binary input, honor whitespace and context options, and can compute word-level spans.
Custom engine
An engine implements
DiffEngineInterface::diff(string $old, string $new, Options $options): DiffResult.
Pass the implementation to Diff::withEngine().
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Engine\DiffEngineInterface;
use Alto\Code\Diff\Engine\MyersDiffEngine;
use Alto\Code\Diff\Model\DiffResult;
use Alto\Code\Diff\Options\Options;
final class AuditedEngine implements DiffEngineInterface
{
public function diff(string $old, string $new, Options $options): DiffResult
{
error_log(sprintf('Comparing %d and %d bytes', strlen($old), strlen($new)));
return (new MyersDiffEngine())->diff($old, $new, $options);
}
}
$result = Diff::build()
->withEngine(new AuditedEngine())
->compare("old\n", "new\n");
echo count($result->hunks())." changed region(s)\n";Word tokenizer
MyersDiffEngine and LcsDiffEngine accept a TokenizerInterface in their
constructors. Its tokenize(string $input): array method returns a list of
tokens. Pass null to disable word spans even when withWordDiff() is enabled.
The built-in WordTokenizer keeps whitespace as tokens so renderers can
reconstruct the original line exactly.