01 · THE IDEAREAD → TRY → LEARN
Why URLs need encoding
A URL gives certain characters special jobs. A / separates path segments, ? starts a query string, & separates parameters, and % introduces an escape. If user data contains one of these characters and you paste it into a URL unchanged, the data can change the structure of the URL. A department named sales and marketing/Miami should be one path segment, but a raw slash makes it look like two.
Percent-encoding fixes this. Each unsafe byte is replaced by % followed by two hexadecimal digits. A space becomes %20, a slash becomes %2F, and a literal percent sign becomes %25.
rawurlencode
rawurlencode(string $string): string encodes a string according to RFC 3986. Every character except ASCII letters, digits and the four characters -, _, . and ~ is replaced by a percent escape. Strings are processed as bytes, so a multibyte UTF-8 character becomes several escapes: é is %C3%A9.
The function always returns a string and has no failure mode. An empty string comes back as an empty string.
echo rawurlencode('sales and marketing/Miami'), "\n";sales%20and%20marketing%2FMiami
The slash is encoded, which is exactly what you want when the whole value is one segment.
rawurlencode versus urlencode
urlencode() produces the older form-style encoding used by application/x-www-form-urlencoded. Two differences matter here: it turns a space into +, and it also encodes ~. In a path, a + is an ordinary character, not a space, so form encoding can corrupt path segments.
$value = 'a b+c~';
echo json_encode([
'raw' => rawurlencode($value),
'form' => urlencode($value),
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), "\n";{"raw":"a%20b%2Bc~","form":"a+b%2Bc%7E"}
Decoding mirrors this. rawurldecode() leaves + alone, while urldecode() converts + to a space. Decode with the partner of the function you used to encode.
Encode the parts, not the whole URL
The most common mistake is encoding a complete URL. Running rawurlencode on https://example.com/a would encode the colon and the slashes and break it. Instead, encode each component separately and then join the pieces with the delimiters you control.
For a path, that means encoding every segment and joining with /:
function pathFromSegments(array $segments): string {
return implode('/', array_map('rawurlencode', $segments));
}
echo pathFromSegments(['files', 'Q1 report', '2024/25']), "\n";files/Q1%20report/2024%2F25
The / inside 2024/25 is data, so it is escaped. The slashes between segments are structure, so they stay.
Edge cases
- An empty segment stays empty, so joining can produce
//. Decide whether your application allows that. - Encoding twice turns
%20into%2520. Encode raw data exactly once, at the moment you build the URL. - Unreserved characters
-_.~pass through unchanged, so there is no need to escape them yourself. - Rawurlencode does not know about character sets. It encodes whatever bytes it receives, so make sure your strings are UTF-8 first.
Exercise contract
Write buildUrlPath(array $segments): string. It must encode every segment with rawurlencode, join the segments with /, and put a single / at the start. An empty array returns just /. The checks cover spaces, an embedded slash, an empty list, unreserved characters, a literal percent sign and a UTF-8 character. Two tempting shortcuts fail: using urlencode turns spaces into +, and encoding the already joined string also encodes the separators.
Recap
Use rawurlencode for URL path components, because it follows RFC 3986 and writes spaces as %20. Use urlencode only for form-style data. Always encode individual components, never a finished URL, and encode each value only once.
Functions · Arrays · Types
- Encode path components with rawurlencode and distinguish form encoding
- Implement and test a deterministic PHP 8.4 function