Multibyte strings7 min readExamples run on PHP 8.5.10

mb_check_encoding()

Learn how mb_check_encoding() tests whether bytes are valid in a given encoding, including recursive array checks, the ValueError case and common pitfalls.

Signature

mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool
Available since PHP 4.4.37.48.08.18.28.38.48.5All versions →

Purpose

A PHP string is only a sequence of bytes. PHP does not record whether those bytes are UTF-8, Latin-1 or something else, and it does not complain when they are malformed. mb_check_encoding() answers one question: is this byte sequence valid in the encoding I expect? It is a cheap validity gate to run before you store, compare or convert text that came from outside your program. Checking early guards against what the manual calls an "Invalid Encoding Attack".

Signature

Parameters and defaults

  • $value is the string to test, or an array. For an array, every key and every value is validated recursively. Both parameters default to null, but passing null or omitting $value is deprecated as of PHP 8.1. In that case the function checks the request input rather than a value you supply. In new code, always pass an explicit value.
  • $encoding is the name of the expected encoding, such as 'UTF-8', 'ASCII' or 'ISO-8859-1'. When it is null, the current mbstring internal encoding is used.

Return value and failure cases

The function returns true when the bytes are valid for the encoding and false when they are not. An invalid string is not an error; it is just a false result. The one exceptional case is an unknown encoding name. Since PHP 8.0 that throws a ValueError, whereas older versions emitted a warning. Because encoding names are usually hard-coded, you rarely need to catch it, but if the name comes from user input you should validate it first.

Example 1: valid and invalid UTF-8

The first string below is a complete UTF-8 character sequence. The second ends in \xC3, which is the first byte of a two-byte sequence with its continuation byte missing.

Example
<?php
$valid = "Caf\u{E9}";
$invalid = "Caf\xC3";

echo json_encode([
    'valid' => mb_check_encoding($valid, 'UTF-8'),
    'invalid' => mb_check_encoding($invalid, 'UTF-8'),
]) . "\n";
Output
{"valid":true,"invalid":false}

Example 2: validity depends on the encoding

The single byte \xE9 is the letter "é" in ISO-8859-1. On its own it is not valid UTF-8, and it is outside the 7-bit ASCII range.

Example
<?php
$byte = "\xE9";

echo json_encode([
    'utf8' => mb_check_encoding($byte, 'UTF-8'),
    'latin1' => mb_check_encoding($byte, 'ISO-8859-1'),
    'ascii' => mb_check_encoding($byte, 'ASCII'),
]) . "\n";
Output
{"utf8":false,"latin1":true,"ascii":false}

This shows that the function does not detect an encoding. It only tests the one you name.

Example 3: checking arrays recursively

Decoded form data or parsed input often arrives as nested arrays. Passing the whole array validates keys and values at every depth, so one call is enough.

Example
<?php
$clean = ['name' => 'Ana', 'tags' => ['php', 'caf\u{E9}']];
$badValue = ['name' => 'Ana', 'tags' => ['php', "bad\xFF"]];
$badKey = ["bad\xFF" => 'value'];

echo json_encode([
    'clean' => mb_check_encoding($clean, 'UTF-8'),
    'badValue' => mb_check_encoding($badValue, 'UTF-8'),
    'badKey' => mb_check_encoding($badKey, 'UTF-8'),
]) . "\n";
Output
{"clean":true,"badValue":false,"badKey":false}

Edge cases

  • An empty string is valid in every supported encoding, so the result is true.
  • Pure ASCII text is valid UTF-8, because UTF-8 was designed to be a superset of ASCII.
  • Truncated sequences, stray continuation bytes and overlong forms such as "\xC0\x80" are all invalid UTF-8.
  • A single bad byte anywhere makes the whole string invalid, and in an array a single bad key or value makes the whole array invalid. The function gives no position, so you cannot tell where the problem is.
  • Some single-byte encodings accept almost any byte, so a true result for such an encoding is weak evidence about the real origin of the text.
  • An unknown encoding name throws ValueError.

Common mistakes

  1. Treating it as detection. true means "valid as the encoding you named", not "this is the encoding". Use mb_detect_encoding() for guessing, and even then be cautious.
  2. Omitting the first argument. Calling it with no value or with null is deprecated since PHP 8.1. Pass the data explicitly.
  3. Relying on the default encoding without thinking. If the internal encoding was changed elsewhere, your check silently tests something else. Pass 'UTF-8' explicitly when that is what you mean.
  4. Checking after conversion. Validate the original input first. If you convert invalid bytes before checking, the problem may be hidden or altered.
  5. Assuming false is an exception. Invalid text just returns false; handle that branch yourself, for example by rejecting the input.
  • mb_detect_encoding() guesses the encoding from a list of candidates.
  • mb_convert_encoding() converts text from one encoding to another.
  • mb_scrub() replaces invalid byte sequences so the result is valid.
  • mb_strlen() counts characters rather than bytes in a given encoding.

Recap

mb_check_encoding() returns a boolean telling you whether a string, or every key and value of an array, is valid in a named encoding. Always pass the value and the encoding explicitly. Remember that validity is relative to the encoding you name, that the function never reports where an error is, and that an unknown encoding name throws a ValueError.

Tested examples

Valid and invalid UTF-8
<?php
$valid = "Caf\u{E9}";
$invalid = "Caf\xC3";

echo json_encode([
    'valid' => mb_check_encoding($valid, 'UTF-8'),
    'invalid' => mb_check_encoding($invalid, 'UTF-8'),
]) . "\n";
Output
{"valid":true,"invalid":false}
Validity depends on the encoding
<?php
$byte = "\xE9";

echo json_encode([
    'utf8' => mb_check_encoding($byte, 'UTF-8'),
    'latin1' => mb_check_encoding($byte, 'ISO-8859-1'),
    'ascii' => mb_check_encoding($byte, 'ASCII'),
]) . "\n";
Output
{"utf8":false,"latin1":true,"ascii":false}
Checking arrays recursively
<?php
$clean = ['name' => 'Ana', 'tags' => ['php', 'caf\u{E9}']];
$badValue = ['name' => 'Ana', 'tags' => ['php', "bad\xFF"]];
$badKey = ["bad\xFF" => 'value'];

echo json_encode([
    'clean' => mb_check_encoding($clean, 'UTF-8'),
    'badValue' => mb_check_encoding($badValue, 'UTF-8'),
    'badKey' => mb_check_encoding($badKey, 'UTF-8'),
]) . "\n";
Output
{"clean":true,"badValue":false,"badKey":false}