Multibyte strings7 min readExamples run on PHP 8.5.10

mb_encode_numericentity()

Learn how mb_encode_numericentity() turns selected multibyte characters into HTML numeric character references, how the conversion map works, and which mistakes to avoid.

Signature

mb_encode_numericentity(
    string $string,
    array $map,
    ?string $encoding = null,
    bool $hex = false
): string
Available since PHP 4.0.67.48.08.18.28.38.48.5All versions →

Purpose

mb_encode_numericentity() replaces selected characters in a string with HTML numeric character references such as é or é. You choose which characters are replaced by supplying a conversion map. Characters outside the map are copied unchanged.

Typical uses are producing pure-ASCII output from Unicode text, or escaping a chosen set of characters for a channel that cannot carry raw multibyte bytes. The inverse operation is mb_decode_numericentity().

Signature

Parameters and defaults

  • $string: the text to convert.
  • $map: a flat array of integers, read in groups of four: start, end, offset, mask. A character whose code point lies between start and end (inclusive) is converted. PHP adds offset to the code point, applies a bitwise AND with mask, and writes the result as a numeric reference. Several groups can be placed one after another to describe several ranges.
  • $encoding: the character encoding of $string. It defaults to null, which means the current internal encoding. Passing "UTF-8" explicitly makes the call independent of configuration.
  • $hex: defaults to false, giving decimal references like あ. With true you get hexadecimal references like あ.

Return value and failures

The function returns the converted string. It throws a ValueError if $map contains a value that is not an int, float, bool, null or numeric string. Since PHP 8.4.0 this check covers any value that cannot be implicitly converted to an int. A ValueError is also thrown when $encoding is not a valid encoding name, which has been the behavior since PHP 8.0.0. In PHP 8.0.0 the $encoding parameter also became nullable.

Example 1: encode everything above ASCII

A map covering code points from 0x80 upwards leaves ASCII alone and converts every other character. The mask 0x1FFFFF is wide enough not to alter any valid code point.

Example
<?php
$map = [0x80, 0x10FFFF, 0, 0x1FFFFF];
echo json_encode(mb_encode_numericentity("caf\u{E9}", $map, "UTF-8"), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"caf&#233;"

The é (code point 233) becomes &#233;, while caf is untouched.

Example 2: decimal and hexadecimal output

The fourth argument switches the notation. The character あ is U+3042, which is 12354 in decimal.

Example
<?php
$map = [0x80, 0x10FFFF, 0, 0x1FFFFF];
$text = "a\u{3042}b";
echo json_encode([
    mb_encode_numericentity($text, $map, "UTF-8"),
    mb_encode_numericentity($text, $map, "UTF-8", true),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["a&#12354;b","a&#x3042;b"]

Example 3: ranges and offsets

The map range selects characters, and the offset is added to each selected code point before the mask is applied. Here A, B and C (65 to 67) are shifted by 256, while D falls outside the range and is copied as is.

Example
<?php
$map = [0x41, 0x43, 0x100, 0xFFFF];
echo json_encode(mb_encode_numericentity("ABCD", $map, "UTF-8"), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"&#321;&#322;&#323;D"

The result is &#321;&#322;&#323;D. An offset of zero is the normal choice when you simply want the real code point. A non-zero offset only makes sense if the receiving side reverses it, for example with a matching mb_decode_numericentity() map.

Edge cases

  • Ranges are inclusive. A group such as 0x3C, 0x3C, 0, 0xFFFF converts only <.
  • Characters outside every range are never touched. An empty map therefore returns the string unchanged.
  • The mask can corrupt code points. The AND operation is applied after the offset. If the mask has fewer bits than the code point needs, the high bits are dropped and the reference names a different character. The manual's example with mask 0x10FFFF shows this for a supplementary character: the printed number is smaller than the real code point. Use a mask such as 0x1FFFFF (or 0x10FFFF only when you intend that truncation) after checking the result.
  • Hexadecimal digits. With $hex set to true, letters in the number may be uppercase or lowercase depending on the implementation, so do not compare hex output case-sensitively against hand-written text.

Common mistakes

  1. Writing the map as nested arrays. The map is one flat list; each rule is four consecutive integers, not a sub-array.
  2. Leaving out a value from a group. Every rule needs all four numbers. Keep complete groups of four.
  3. Putting non-numeric values in the map. Strings such as "abc" cause a ValueError.
  4. Relying on the default encoding. If the internal encoding differs from your data, characters are interpreted incorrectly. Pass the encoding explicitly.
  5. Treating it as HTML escaping. It is not a replacement for htmlspecialchars(). It only converts what your map selects. To escape <, > or & you must list them in the map yourself.
  6. Forgetting to decode with a compatible map. If you used a non-zero offset, the decoding map must undo it.
  • mb_decode_numericentity(): converts numeric references back to characters.
  • htmlspecialchars() and htmlentities(): escape HTML-significant characters using named entities.
  • mb_convert_encoding(): converts text between character encodings, with an HTML-entities option.

Recap

Build a flat map of start, end, offset, mask groups, pass the encoding explicitly, and choose a mask wide enough for the code points involved. Use $hex to switch between &#233; and &#xE9; style output, and expect a ValueError for non-numeric map values or an invalid encoding.

Tested examples

Encode every non-ASCII character as a decimal reference
<?php
$map = [0x80, 0x10FFFF, 0, 0x1FFFFF];
echo json_encode(mb_encode_numericentity("caf\u{E9}", $map, "UTF-8"), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"caf&#233;"
Decimal versus hexadecimal output
<?php
$map = [0x80, 0x10FFFF, 0, 0x1FFFFF];
$text = "a\u{3042}b";
echo json_encode([
    mb_encode_numericentity($text, $map, "UTF-8"),
    mb_encode_numericentity($text, $map, "UTF-8", true),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
["a&#12354;b","a&#x3042;b"]
Using a range with an offset
<?php
$map = [0x41, 0x43, 0x100, 0xFFFF];
echo json_encode(mb_encode_numericentity("ABCD", $map, "UTF-8"), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";
Output
"&#321;&#322;&#323;D"