In modern web development, obfuscating or “masking” sensitive user data (such as credit card numbers, phone numbers, email addresses, and national IDs) is a daily necessity for security and privacy compliance (e.g., GDPR). Currently, PHP developers have to rely on a combination of functions like substr, str_repeat, str_pad, or regular expressions (preg_replace). These user-land implementations reduce code readability and are prone to off-by-one errors if not carefully crafted.
The goal of introducing the str_mask() function is to provide a native, fast, and standard tool in the PHP core to perform string masking with the highest performance and the cleanest syntax possible. This function serves as the byte-level counterpart to the grapheme_mask() function proposed for the intl extension, together providing a complete toolkit for string masking in both ASCII and multi-byte environments.
The new str_mask function will be defined in the global namespace with the following signature:
function str_mask( string $string, string $mask_char = '*', int $offset = 0, ?int $length = null ): string {}
Returns the masked string. If the input string is empty, an empty string is returned. If offset is out of bounds, the original string is returned unchanged.
Mask all digits of a 16-digit credit card except for the first 4 and the last 4 digits.
$credit_card = '1234567890123456'; $masked_card = str_mask($credit_card, '*', 4, 8); echo $masked_card; // Output: 1234********3456
Hide the last 4 digits of a phone number using a negative offset. This is particularly useful when the total length of the string may vary.
$phone_number = '+989123456789'; $masked_phone = str_mask($phone_number, 'X', -4); echo $masked_phone; // Output: +9891234XXXX
Keep the first letter visible, mask everything up to the @ symbol, and leave the domain intact. The negative length allows us to stop masking before the domain without calculating the exact length.
$email = 'sepehr.developer@example.com'; $at_position = strpos($email, '@'); $distance_from_end = -(strlen($email) - $at_position); $masked_email = str_mask($email, '*', 1, $distance_from_end); echo $masked_email; // Output: s***************@example.com
If the offset is beyond the string length, the function returns the original string unchanged.
$api_token = 'AB'; $masked_short = str_mask($api_token, '*', 3); echo $masked_short; // Output: AB
Using the default mask character (*) and masking from the beginning to the end.
$text = 'Hello World'; $masked = str_mask($text); echo $masked; // Output: ***********
None. This is a new function added to the core and does not break any existing functionality.
PHP 8.7
This function serves as the byte-level counterpart to the grapheme_mask() function proposed for the intl extension in a separate RFC. Together, they will provide a complete toolkit for string masking in both ASCII and multi-byte environments.
As this is a feature addition, it requires a 2/3 majority in favor to be accepted. Voting will open on 2026-07-20 and close on 2026-08-03.