====== PHP RFC: Remove \0 from default trim() character mask ======
* Version: 1.0
* Date: 2026-03-15
* Author: Weilin Du, lamentxu@163.com
* Status: Under Discussion
===== Introduction =====
The trim(), ltrim(), and rtrim() functions strip whitespace (or other characters) from the beginning and end of a string. When the second parameter ($characters) is omitted, these functions use a default set of characters.
Currently, this default set includes (after the implemented RFC adding form feed https://wiki.php.net/rfc/trim_form_feed):
* “ ” (ASCII 32 (0x20)), an ordinary space.
* “\t” (ASCII 9 (0x09)), a tab.
* “\n” (ASCII 10 (0x0A)), a new line (line feed).
* “\r” (ASCII 13 (0x0D)), a carriage return.
* “\0” (ASCII 0 (0x00)), the NUL-byte.
* “\v” (ASCII 11 (0x0B)), a vertical tab.
* “\f” (ASCII 12 (0x0C)), a form feed.
This proposal suggests modifying the default character mask of the trim(), ltrim(), and rtrim() functions to no longer include the NUL byte (\0). Treating a NUL byte as a typographical whitespace character is an anomaly among modern programming languages. It frequently leads to silent data corruption and unexpected bugs when developers use trim() to clean input that may contain legitimate binary data.
Removing \0 from the default mask aligns PHP with other major languages and makes the function safer for general-purpose string and payload manipulation.
===== Proposal =====
This RFC proposes to remove \0 (ASCII 0 (0x00)) from the default character mask of trim(), ltrim(), and rtrim().
The core arguments for this change are as follows:
Firstly. Semantically, whitespace characters are intended for typographical spacing and formatting (e.g., spaces, newlines, tabs). The NUL byte is a control character, historically used as a string terminator in C or as padding in binary structures. It is not a typographical whitespace character and should not be stripped by default.
Secondly. Payloads frequently contain raw binary data where the NUL byte is highly meaningful (which happens in my own case, and that's why I am proposing this change). If a developer uses trim() to casually remove accidental trailing newlines from a binary stream or cryptographic hash, the payload is silently truncated, causing signature validation failures or corrupted hardware instructions.
Thirdly. PHP's behavior here creates a "surprising case" for developers. Standard trimming functions in almost all other widespread languages, including Python (str.strip()), JavaScript (String.prototype.trim()), Ruby (String#strip), and Go (strings.TrimSpace()), and even glibc (is_space()), do not classify the NUL byte as whitespace. Aligning PHP with these ecosystem standards reduces friction and cognitive load.
Developers who explicitly need to strip NUL bytes can still achieve this by providing a custom character mask as the second argument. I personally think only a small group of folks would assume NUL will be trimmed in PHP due to aforementioned reasons.
==== Examples ====
Standard string trimming remains unaffected:
Example showing the change with binary data:
===== Backward Incompatible Changes =====
This is a backward-incompatible change. Any code that relies on trim(), ltrim(), or rtrim() to implicitly remove NUL bytes will be affected.
The impact is expected to be relatively low in typical web applications but could affect legacy scripts that rely on this undocumented quirk to clean up C-style strings from database drivers or external FFIs.
To mitigate this, static analysis tools (like PHPStan or Psalm) and automated refactoring tools (like Rector) can easily detect calls to trim() missing a second argument and automatically inject the legacy mask (" \f\n\r\t\v\0") if backward compatibility must be strictly preserved during upgrades.
===== Proposed PHP Version(s) =====
PHP 8.6
===== RFC Impact =====
==== To the Ecosystem ====
Static analyzers and IDEs will need to update their internal stubs for trim() to reflect the new default mask behavior. Automated refactoring tools can easily provide rules to assist with migrations.
==== To Existing Extensions ====
(I guess) Internal C code utilizing php_trim() may behave differently if the default mask is used.
===== Voting Choices =====
The voting process is not started yet.
Primary Vote requiring a 2/3 majority to accept the RFC:
* Yes
* No
* Abstain
===== Patches and Tests =====
TBD. The Implementation is simple so no worries about this.
===== References =====
Internal Discussion: https://externals.io/message/130318
===== Changelog =====
2026/03/15: Initial version.