# RFC: grapheme_str_pad()

- Status: Under discussion
- Authors: Chris Abner Herrera Navarro, chrisabner@hotmail.com
- Date: 2026-11-03
- Version: 0.1
- Target: PHP 8.7
- Implementation: https://github.com/php/php-src/pull/25179
- Original: https://wiki.php.net/rfc/grapheme_str_pad
- Page: https://externals.io/rfc/grapheme_str_pad
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Add grapheme_str_pad() as outlined in the RFC? (closed)

Yes: 0 · No: 0 · Abstain: 0

## Discussions on the mailing list

- [\[RFC\] grapheme_str_pad()](https://externals.io/message/132995.md) — started on 2026-11-03, 1 messages

---

## Introduction

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](https://externals.io/rfc/mb_str_pad).

The same visible text should get the same padding:

```php
<?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é.."
?>
```

## Proposal

Add the following function to ext/intl:

```php
<?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()`:

- If `$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.
- For ASCII input the result is identical to `str_pad()`. The two-byte sequence `“\r\n”` is a single grapheme cluster, as in all `grapheme_*` functions.

### Why not mb\_str\_pad()

`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
<?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()`.

### Doing it in userland

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.

### Error conditions

Same as `mb_str_pad()`:

- An empty `$pad_string` throws a `ValueError`, unless no padding is needed.
- A `$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
<?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
?>
```

### Examples

```php
<?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
?>
```

## Backward Incompatible Changes

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.

## Proposed PHP Version(s)

Next PHP 8.x (PHP 8.7).

## RFC Impact

### To the Ecosystem

None beyond knowing the new function.

### To Existing Extensions

Adds `grapheme_str_pad()` to ext/intl. No change to existing functions.

### To SAPIs

None.

## Open Issues

None.

## Future Scope

- Padding to a display width (where East Asian wide characters and many emoji take two terminal columns, see `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.

## Voting Choices

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)

## Patches and Tests

Implementation with tests: <https://github.com/php/php-src/pull/25179>

## Implementation

After the RFC is implemented, this section will contain:

1. the version(s) it was merged into
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

- [mb\_str\_pad RFC](https://externals.io/rfc/mb_str_pad), Future Scope: “In the future we could add a string padding function that works on grapheme clusters instead of code points: grapheme\_str\_pad(). This should be added to ext/intl. This will of course require another RFC.”
- [GH-10203](https://github.com/php/php-src/issues/10203), where a grapheme cluster variant of mb\_str\_pad() was first suggested.
- [grapheme\_str\_split RFC](https://externals.io/rfc/grapheme_str_split) and [grapheme\_strrev RFC](https://externals.io/rfc/grapheme_strrev), previous grapheme counterparts of string functions.
- Discussion thread: <https://news-web.php.net/php.internals/132995>

## Rejected Features

None yet.

## Changelog

- 0.1 (2026-11-03): Initial draft.
