Copied from wiki.php.net, last synchronized Nov 3, 2026 23:46 UTC
PHP has a padding function for bytes, str_pad(), and one for code points, mb_str_pad() (PHP 8.3). It has no padding function for grapheme clusters, the unit the grapheme_* functions of ext/intl use for “a character as a user sees it”. This RFC proposes grapheme_str_pad(), as already suggested in the Future Scope of the mb_str_pad RFC.
The same visible text should get the same padding:
<?php
$nfc = "Caf\u{00E9}"; // "Café", é as one code point (U+00E9)
$nfd = "Cafe\u{0301}"; // "Café", e followed by U+0301 COMBINING ACUTE ACCENT
var_dump(mb_str_pad($nfc, 6, '.')); // string(7) "Café.."
var_dump(mb_str_pad($nfd, 6, '.')); // string(7) "Café." one dot less
var_dump(grapheme_str_pad($nfc, 6, '.')); // string(7) "Café.."
var_dump(grapheme_str_pad($nfd, 6, '.')); // string(8) "Café.."
?>
Add the following function to ext/intl:
<?php
function grapheme_str_pad(string $string, int $length, string $pad_string = " ", int $pad_type = STR_PAD_RIGHT): string|false {}
?>
It pads $string on the left, the right or both sides with $pad_string until the result is $length grapheme clusters long. $length and the length of the input are counted in grapheme clusters, as grapheme_strlen() counts them.
The function mirrors mb_str_pad() and str_pad():
$length is negative, or less than or equal to the length of $string, $string is returned unchanged.$pad_type is one of the existing constants STR_PAD_LEFT, STR_PAD_RIGHT (default) or STR_PAD_BOTH. With STR_PAD_BOTH the extra grapheme cluster, if any, goes to the right, like in str_pad().$pad_string is repeated as often as needed. If the last copy does not fit entirely, it is cut on a grapheme cluster boundary. The padding string is never split inside a grapheme cluster.str_pad(). The two-byte sequence “\r\n” is a single grapheme cluster, as in all grapheme_* functions.mb_str_pad() counts code points. Many characters a user perceives as one are made of several code points: letters with combining marks (text in Unicode normalization form NFD, which some platforms and input methods produce), Japanese kana with a combining dakuten (が = か + U+3099), most Indic scripts, emoji with skin tone modifiers and emoji ZWJ sequences. For such text mb_str_pad() adds too little padding, and it can split the padding string inside a grapheme cluster, which changes the padding text itself:
<?php
// Padding string "üö" in NFD: u + U+0308, o + U+0308 (4 code points, 2 grapheme clusters)
var_dump(mb_str_pad("a", 4, "u\u{0308}o\u{0308}")); // string(5) "aüo" ö lost its diaeresis
var_dump(grapheme_str_pad("a", 4, "u\u{0308}o\u{0308}")); // string(10) "aüöü"
$ga = "\u{304B}\u{3099}\u{304E}"; // "がぎ": が is か + U+3099, ぎ is precomposed
var_dump(mb_str_pad($ga, 4, '*')); // string(10) "がぎ*"
var_dump(grapheme_str_pad($ga, 4, '*')); // string(11) "がぎ**"
var_dump(mb_str_pad("👍🏽", 3, '-', STR_PAD_LEFT)); // string(9) "-👍🏽"
var_dump(grapheme_str_pad("👍🏽", 3, '-', STR_PAD_LEFT)); // string(10) "--👍🏽"
?>
The grapheme_* functions exist precisely for this unit. Adding the padding function completes the set of byte, code point and grapheme cluster variants, as grapheme_str_split() (PHP 8.4) and grapheme_strrev() (PHP 8.6) did for str_split() and strrev().
A correct userland version needs grapheme_strlen() for the input, grapheme_strlen() and grapheme_substr() for the padding string, and the same left/right arithmetic as str_pad(). Symfony's String component does exactly this in UnicodeString::padStart(), padEnd() and padBoth(), whose length is grapheme_strlen(). Everybody else has to reimplement it, or use mb_str_pad() and get the results shown above.
Same as mb_str_pad():
$pad_string throws a ValueError, unless no padding is needed.$pad_type other than STR_PAD_LEFT, STR_PAD_RIGHT or STR_PAD_BOTH throws a ValueError.Like grapheme_strlen(), invalid UTF-8 in $string or $pad_string makes the function return false and sets the intl error (intl_get_error_code() / intl_get_error_message()).
<?php
grapheme_str_pad("abc", 5, "");
// ValueError: grapheme_str_pad(): Argument #3 ($pad_string) must not be empty
grapheme_str_pad("abc", 5, "-", 42);
// ValueError: grapheme_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH
var_dump(grapheme_str_pad("abc\xFF", 6, ".")); // bool(false)
echo intl_get_error_message();
// grapheme_str_pad(): Invalid UTF-8 string: U_INVALID_CHAR_FOUND
?>
<?php
var_dump(grapheme_str_pad("5", 3, "0", STR_PAD_LEFT)); // string(3) "005", same as str_pad()
var_dump(grapheme_str_pad("ok", 5, "👨👩👧", STR_PAD_BOTH)); // "👨👩👧ok👨👩👧👨👩👧": 5 grapheme clusters
?>
A userland function named grapheme_str_pad() in the global namespace would conflict. No definitions were found on 2026-11-03 with GitHub code search (“function grapheme_str_pad(”, PHP) or Sourcegraph (grapheme_str_pad, PHP). No other changes.
Next PHP 8.x (PHP 8.7).
None beyond knowing the new function.
Adds grapheme_str_pad() to ext/intl. No change to existing functions.
None.
None.
mb_strwidth()) is a different problem and is not addressed by this RFC. grapheme_str_pad() counts grapheme clusters, consistently with the other grapheme_* functions.Primary Vote requiring a 2/3 majority to accept the RFC:
Vote: Add grapheme_str_pad() as outlined in the RFC? — Yes: 0 · No: 0 · Abstain: 0 (closed)
Implementation with tests: https://github.com/php/php-src/pull/25179
After the RFC is implemented, this section will contain:
None yet.