URL Encode / Decode — PHP source
Percent-encode or decode URLs and query parameters. Choose component (encodeURIComponent) or full-URI (encodeURI) mode. 100% client-side.
This is the PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* URL encode / decode — component-level (encodeURIComponent) and full URI (encodeURI).
*
* Language: PHP
* CosmoDev polyglot showcase port of the `url-encode` tool.
* Ported from src/tools/UrlEncodeTool.tsx — display source, part of CosmoDev's
* polyglot tool pages.
*
* Explicit percent-encoding matching JavaScript's encodeURIComponent/encodeURI.
* PHP strings are byte arrays, so we walk bytes: each non-safe byte becomes
* %XX (uppercase hex). Component scope leaves A-Za-z0-9-_.!~*'() unescaped;
* full URI scope additionally leaves the reserved set ;,/?:@&=+$# unescaped.
* Decode reverses this, returning null on malformed % sequences; full URI
* leaves encoded reserved bytes intact. PHP's rawurlencode differs from
* encodeURIComponent on !*'(), so this is hand-rolled for exact parity.
*/
/**
* True when a byte is never escaped by encodeURIComponent.
*/
function url_encode_is_component_safe(int $c): bool
{
if (($c >= 0x41 && $c <= 0x5A) || ($c >= 0x61 && $c <= 0x7A) || ($c >= 0x30 && $c <= 0x39)) {
return true;
}
return strpos("-_.!~*'()", chr($c)) !== false;
}
function url_encode_is_uri_extra(int $c): bool
{
return strpos(";,/?:@&=+$#", chr($c)) !== false;
}
/**
* Percent-encode $input. When $fullUri is true it matches encodeURI;
* otherwise it matches encodeURIComponent.
*/
function url_encode(string $input, bool $fullUri = false): string
{
$out = '';
$len = strlen($input);
for ($i = 0; $i < $len; $i++) {
$c = ord($input[$i]);
if (url_encode_is_component_safe($c) || ($fullUri && url_encode_is_uri_extra($c))) {
$out .= chr($c);
} else {
$out .= '%' . strtoupper(bin2hex(chr($c)));
}
}
return $out;
}
/**
* Read the two hex digits after '%' at index $i. Returns the byte value or null.
*/
function url_encode_unhex_pair(string $s, int $i): ?int
{
if ($i + 2 >= strlen($s) || !ctype_xdigit($s[$i + 1]) || !ctype_xdigit($s[$i + 2])) {
return null;
}
return (hexdec($s[$i + 1]) << 4) | hexdec($s[$i + 2]);
}
/**
* Percent-decode $input. Returns null on malformed sequences.
* When $fullUri is true it matches decodeURI (encoded reserved bytes preserved).
*/
function url_decode(string $input, bool $fullUri = false): ?string
{
$reserved = ";/?:@&=+$#";
$out = '';
$len = strlen($input);
$i = 0;
while ($i < $len) {
if ($input[$i] !== '%') {
$out .= $input[$i];
$i++;
continue;
}
$b = url_encode_unhex_pair($input, $i);
if ($b === null) {
return null;
}
if ($b < 0x80) {
if ($fullUri && strpos($reserved, chr($b)) !== false) {
$out .= substr($input, $i, 3);
} else {
$out .= chr($b);
}
$i += 3;
continue;
}
// Multi-byte UTF-8 lead byte.
if (($b & 0xE0) === 0xC0) {
$seqLen = 2;
} elseif (($b & 0xF0) === 0xE0) {
$seqLen = 3;
} elseif (($b & 0xF8) === 0xF0) {
$seqLen = 4;
} else {
return null;
}
$buf = chr($b);
for ($n = 1; $n < $seqLen; $n++) {
$pi = $i + 3 * $n;
if ($pi >= $len || $input[$pi] !== '%') {
return null;
}
$cb = url_encode_unhex_pair($input, $pi);
if ($cb === null || ($cb & 0xC0) !== 0x80) {
return null;
}
$buf .= chr($cb);
}
$out .= $buf;
$i += 3 * $seqLen;
}
return $out;
}
// Demo
echo url_encode('hello world & café', false), "\n";
var_dump(url_decode('hello%20world%20%26%20caf%C3%A9', false));
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →