Strings7 min readExamples run on PHP 8.5.10

similar_text()

Learn how PHP's similar_text() counts matching characters, how the percent argument works, why argument order matters, and where it falls short.

Signature

similar_text(string $string1, string $string2, float &$percent = null): int

Purpose

similar_text() measures how alike two strings are. It returns the number of characters the strings have in common, and it can also give you that number as a percentage through a by-reference argument. It is handy for rough "did you mean...?" suggestions, loose matching of short labels, and quick sanity checks on user input.

Signature

Parameters and defaults

  • $string1: the first string.
  • $string2: the second string.
  • $percent: optional. If you pass a variable, PHP stores the similarity as a float in it. The value is the match count divided by the average of the two string lengths, times 100. In effect it is sim * 2 / (len1 + len2) * 100.

Because $percent is a reference, you must pass a variable, not a literal or an expression.

Return value and failure cases

The function returns an int: the total length of the matching pieces. It does not return false. If both strings are empty, the result is 0 and the percent is set to 0.

The algorithm works like this: find the longest common substring, then repeat the process on the parts to the left and to the right of that match, and add up the lengths of everything found. Comparison is case-sensitive and works on bytes, so it is not aware of multibyte characters.

In the default non-strict mode, scalar arguments are converted to strings. Passing an array, or an object that cannot be converted to a string, raises a TypeError.

Example 1: count and percent

World and Word share Wor, and then the leftover ld and d share d. That makes 4 matching characters. The percent is 4 * 2 / (5 + 4) * 100, about 88.89.

Example
<?php
$sim = similar_text('World', 'Word', $percent);
echo json_encode(['sim' => $sim, 'percent' => round($percent, 2)]) . "\n";
Output
{"sim":4,"percent":88.89}

Rounding the percent keeps output stable and readable, because the raw float has many decimal places.

Example 2: argument order matters

The algorithm is not symmetric. Which string comes first can change which common substring is picked first, and that changes the recursion on each side.

Example
<?php
$a = similar_text('bafoobar', 'barfoo', $p1);
$b = similar_text('barfoo', 'bafoobar', $p2);
echo json_encode([
    'first' => ['similarity' => $a, 'percent' => round($p1, 2)],
    'second' => ['similarity' => $b, 'percent' => round($p2, 2)],
]) . "\n";
Output
{"first":{"similarity":5,"percent":71.43},"second":{"similarity":3,"percent":42.86}}

The same two strings give 5 in one order and 3 in the other. If you need a stable score, define a fixed order, for example by sorting the two strings first.

Example 3: choosing the closest candidate

A common use is ranking candidates against a target. Here colour shares colo plus a trailing r with color, so it wins.

Example
<?php
$target = 'color';
$scores = [];
foreach (['colour', 'collar', 'dolor'] as $candidate) {
    $scores[$candidate] = similar_text($target, $candidate);
}
$best = array_search(max($scores), $scores);
echo json_encode(['scores' => $scores, 'best' => $best]) . "\n";
Output
{"scores":{"colour":5,"collar":4,"dolor":4},"best":"colour"}

When two candidates tie, array_search() returns the first one, so decide up front how ties should be handled.

Edge cases

  • Case sensitivity: 'PHP' and 'php' have no matching bytes. Normalize case first, for example with strtolower(), if you want case-insensitive scoring.
  • Empty input: if one string is empty, the count is 0. If both are empty, the percent is 0 too, so you will not hit a division by zero.
  • Repeated characters: only non-overlapping pieces found by the recursive split are counted. A character in one string cannot be matched twice.
  • Multibyte text: accented or non-Latin characters are compared as bytes, so results can look odd for UTF-8 text.
  • Performance: the documented complexity is O(N**3) in the length of the longer string. Avoid running it over very long strings or very large candidate lists.

Common mistakes

  1. Assuming symmetry. Comparing A, B and B, A may give different scores, as Example 2 shows.
  2. Passing a non-variable as $percent. The argument is by reference, so it needs a real variable.
  3. Treating the count as a distance. A bigger number means more similar, the opposite of levenshtein(), where smaller means more similar.
  4. Comparing raw counts across different lengths. A count of 4 means something different for two-word phrases than for long sentences. Use the percent for that.
  5. Printing the raw percent. The float has many digits. Round it before display or comparison.
  6. Using it for phonetic matching. It compares characters, not sounds. For sound-based comparison, look at soundex() or metaphone().
  • levenshtein(): the number of edits needed to turn one string into another.
  • soundex(): a phonetic key for English-like words.
  • metaphone(): a more refined phonetic key.
  • strcmp(): exact, ordered comparison.

Recap

similar_text() returns the total length of the matching pieces found by repeatedly taking the longest common substring and recursing on both sides. The optional by-reference $percent gives a relative score. The result depends on argument order, is case-sensitive and byte-based, and gets slow on long input. Use it for rough ranking of short strings, and pick another function when you need edit distance or phonetic matching.

Tested examples

Match count and percent
<?php
$sim = similar_text('World', 'Word', $percent);
echo json_encode(['sim' => $sim, 'percent' => round($percent, 2)]) . "\n";
Output
{"sim":4,"percent":88.89}
Swapping arguments changes the result
<?php
$a = similar_text('bafoobar', 'barfoo', $p1);
$b = similar_text('barfoo', 'bafoobar', $p2);
echo json_encode([
    'first' => ['similarity' => $a, 'percent' => round($p1, 2)],
    'second' => ['similarity' => $b, 'percent' => round($p2, 2)],
]) . "\n";
Output
{"first":{"similarity":5,"percent":71.43},"second":{"similarity":3,"percent":42.86}}
Choosing the closest candidate
<?php
$target = 'color';
$scores = [];
foreach (['colour', 'collar', 'dolor'] as $candidate) {
    $scores[$candidate] = similar_text($target, $candidate);
}
$best = array_search(max($scores), $scores);
echo json_encode(['scores' => $scores, 'best' => $best]) . "\n";
Output
{"scores":{"colour":5,"collar":4,"dolor":4},"best":"colour"}