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 also says $string would be marked with #[SensitiveParameter], which keeps it out of stack traces.
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
The RFC is a draft targeting PHP 8.7, and the page was last dated September 18, 2026. There's no patch yet and no vote has been held. It would need a two-thirds majority to pass.