mb_scrub

Replaces every invalid byte sequence in a string with the configured substitute character.

Signature
mb_scrub(string $string, ?string $encoding = null): string
  • Added in PHP 7.2.0

Parameters

ParameterTypeDescription
$stringstringThe input string.
$encodingoptional?string= nullThe encoding used to interpret the string. If omitted or null, mbstring.internal_encoding is used if set, otherwise default_charset.

Return value

stringA copy of the string in which every invalid byte sequence has been replaced by the substitute character.

Errors

Throws a ValueError if encoding is not a name mbstring recognises.

Changelog

  • PHP 8.0.0encoding became nullable.

mb_scrub() converts a string from an encoding to itself. That sounds pointless until you realise what the conversion machinery does with bytes it cannot interpret: it replaces them with the substitute character. The result is a string guaranteed to be valid in that encoding, with the damage confined to visible placeholders.

Use it when you have to keep going. Log lines, user comments and scraped text often arrive with a few bytes mangled by a truncated read or a mismatched database column, and rejecting the whole record would lose real content. Scrubbing keeps the good ninety-nine percent and marks the rest. It is also the fix for json_encode() quietly returning false, and for a response body that stops mid-sentence because a broken sequence reached the browser.

What it replaces with is governed by mb_substitute_character(), which defaults to ?. Setting it to 0xFFFD gives you the standard replacement character �, which is easier to spot in logs and easier to search for. That setting is process-wide, so change it once at bootstrap rather than in a helper.

Scrubbing is not repair. Once the bytes are replaced the original characters are unrecoverable, so if the text is really in another encoding, use mb_convert_encoding() instead — scrub only what you have already given up on decoding.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Make a log line safe to store after a truncated read left a half-written character.
  • Sanitise user comments so json_encode() cannot fail on the whole response.
  • Clean rows from a legacy database column of unreliable encoding before display.
  • Guarantee that scraped page text is valid UTF-8 before indexing it.

Frequently asked questions

How do I fix invalid UTF-8 in a PHP string?

Call mb_scrub($string, 'UTF-8'), which replaces every invalid byte sequence with the substitute character and returns a string guaranteed valid. If the text is genuinely in another encoding, use mb_convert_encoding() instead, since scrubbing discards the original characters.

What is the difference between mb_scrub and mb_convert_encoding?

mb_convert_encoding() re-encodes text from one encoding to another and preserves the characters; mb_scrub() converts an encoding to itself purely to strip out the byte sequences that are not valid in it.

How do I make mb_scrub use the � replacement character?

Call mb_substitute_character(0xFFFD) before scrubbing; the default is 0x3F, an ASCII question mark. The setting applies to the whole request, so set it once during bootstrap.

More examples

Rescue a payload that json_encode rejects

json_encode() returns false rather than throwing when any string is not valid UTF-8, so one bad byte in one field silently kills the whole response. Scrubbing that field replaces 0xE9 with the substitute character and the encode succeeds, giving "Jos?" instead of nothing at all.

Example · php
<?php
$row = ['name' => "Jos\xE9", 'city' => 'Berlin'];

var_dump(json_encode($row));
echo json_last_error_msg(), "\n";

$row['name'] = mb_scrub($row['name'], 'UTF-8');
echo json_encode($row), "\n";

Scrub against check, and multiple bad runs

Two separate truncated sequences, a lone lead byte 0xC3 and an incomplete three-byte sequence, are each replaced independently while the surrounding ASCII survives untouched. The second check confirms the output is now valid, so mb_strlen() and every other mbstring function can be trusted on it.

Example · php
<?php
mb_substitute_character(0xFFFD);

$chunk = "start\xC3(mid)\xE2\x82 end";

var_dump(mb_check_encoding($chunk, 'UTF-8'));
$fixed = mb_scrub($chunk, 'UTF-8');

var_dump(mb_check_encoding($fixed, 'UTF-8'));
echo $fixed, "\n";
echo mb_strlen($fixed, 'UTF-8'), " characters\n";

Choosing the substitute character

The default 0x3F writes a plain question mark, 0xFFFD writes the three-byte replacement character � which is far easier to grep for in logs, and 'none' drops the bad bytes entirely so the output shrinks. The setting is global for the request, which is why the last line puts it back.

Example · php
<?php
$input = "x\xFFy";

foreach ([0x3F, 0xFFFD, 'none'] as $mode) {
    mb_substitute_character($mode);
    $out = mb_scrub($input, 'UTF-8');
    printf("%-6s %-12s %d bytes\n", var_export($mode, true), bin2hex($out), strlen($out));
}

mb_substitute_character(0x3F);

Discussion

  • Be the first to comment on this lesson.