Apps often hide part of a string before displaying or logging it, like a card number shown as ************5678. PHP has no built-in way to do this, so this RFC from Sepehr Mahmoudi adds a str_mask() function.
Show me
Today you'd combine substr_replace() and str_repeat():
$masked = substr_replace($input, str_repeat('*', $maskLength), $offset, $length);
With the RFC, you'd write:
$masked = str_mask($input, '*', $offset, $length);
Here are the RFC's examples:
echo str_mask("1234567812345678", "*", 0, -4); // ************5678 echo str_mask("0012345678", "#", 2, 6); // 00######78 echo str_mask("secret_token_abc", "X", 7); // secret_XXXXXXXXX
How it works
function str_mask( string $string, string $mask_char = '*', int $offset = 0, ?int $length = null ): string {}
$mask_charmust be exactly one byte. An empty string or a multibyte character, like an emoji, throws aValueError.$offsetis where masking starts, and a negative value counts from the end.$lengthis how many bytes to mask.nullmeans mask to the end, and a negative value stops that many bytes before the end.
The function "fails closed." If the offset or length is out of range, it throws a ValueError instead of returning the original string, so a bug in your math can't leak the data you meant to hide:
// Throws ValueError: Offset is out of bounds str_mask("hello", "*", 10);
Like str_pad(), the function works on bytes. Masking by visible character could come later in the intl extension, as something like grapheme_mask().
The RFC points to similar framework helpers, like CakePHP's Text::mask() and Laravel's Str::mask().
What it means for existing code
Nothing breaks unless you've already declared your own global str_mask() function, which would clash.
Where it stands
Sepehr Mahmoudi announced the RFC on the internals list on September 18, 2026, though the wiki page still says Draft. Pratik Bhujel pointed out that the first version returned the string unmasked when the offset was out of range, and used only the first byte of the mask character. Sepehr has since revised the RFC to throw a ValueError in both cases. He also dropped #[SensitiveParameter] from the signature, saying sensitivity belongs to the caller's data, not to the function. Other replies question whether the function belongs in core when userland code can do the same job.
It targets PHP 8.7, and there's no implementation yet. A vote would need a two-thirds majority.