PHP RFC: str_mask()
- Version: 1.0
- Date: 2026-07-01
- Author: Sepehr Mahmoudi, sepehrphpr@gmail.com
- Status: Draft
- Target Version: PHP 8.7
Introduction
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.
Proposal
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 {}
Parameters
- $string: The input string being masked. If empty, an empty string is returned.
- $mask_char: The character used to mask the specified portion of the string. The default value is an asterisk (*). If a string longer than one byte is provided, only the first byte is used.
- $offset: The starting position of the mask. This parameter is highly flexible:
- If positive, masking begins at this zero-based index from the start of the string.
- If negative, masking begins at that many characters from the end of the string. For example, an offset of -4 means masking starts at the fourth character from the end. This is extremely useful when the exact length of the string is unknown, but the tail end needs to be modified.
- If the absolute value of offset is greater than the string length, the function returns the original string unchanged.
- $length: The length of the portion to be masked:
- If null (default), masking continues from the $offset until the end of the string.
- If positive, exactly this many characters are masked starting from the $offset.
- If negative, masking starts at the $offset and stops exactly this many characters from the end of the string.
- If 0, no changes are made, and the original string is returned.
Return Value
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.
Examples
Example 1: Masking a Credit Card Number
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
Example 2: Masking a Phone Number (Negative Offset)
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
Example 3: Masking an Email Username (Negative Length)
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
Example 4: Handling Out of Bounds Offsets
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
Example 5: Masking with Default Parameters
Using the default mask character (*) and masking from the beginning to the end.
$text = 'Hello World'; $masked = str_mask($text); echo $masked; // Output: ***********
Backward Incompatible Changes
None. This is a new function added to the core and does not break any existing functionality.
Proposed PHP Version(s)
PHP 8.7
RFC Impact
- SAPIs: No impact.
- Existing Extensions: No impact.
- Opcache: No impact.
- Performance: The function is implemented in C and is significantly faster than user-land implementations.
Future Scope
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.
Voting
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.