php实现punycode
Punycode 简介
Punycode 是一种将 Unicode 字符串转换为 ASCII 字符串的编码方式,常用于国际化域名(IDN)的转换。PHP 提供了内置函数实现 Punycode 编码和解码。

使用 idn_to_ascii 和 idn_to_utf8
PHP 内置了 idn_to_ascii 和 idn_to_utf8 函数,可以直接用于 Punycode 转换。

编码(Unicode 转 Punycode)
$unicodeDomain = '例子.测试';
$punycodeDomain = idn_to_ascii($unicodeDomain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
echo $punycodeDomain; // 输出: xn--fsq.xn--0zwm56d
解码(Punycode 转 Unicode)
$punycodeDomain = 'xn--fsq.xn--0zwm56d';
$unicodeDomain = idn_to_utf8($punycodeDomain, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
echo $unicodeDomain; // 输出: 例子.测试
检查扩展是否启用
确保 PHP 安装了 intl 扩展:
if (!extension_loaded('intl')) {
echo 'intl 扩展未启用,请安装或启用。';
}
手动实现 Punycode
如果无法使用 intl 扩展,可以手动实现 Punycode 编码和解码。以下是一个简单的实现示例:
Punycode 编码函数
function punycode_encode($input) {
$output = '';
$chars = preg_split('//u', $input, -1, PREG_SPLIT_NO_EMPTY);
$n = 128;
$delta = 0;
$bias = 72;
$basic = '';
foreach ($chars as $char) {
if (ord($char) < 128) {
$basic .= $char;
}
}
if ($basic !== '') {
$output = $basic . '-';
}
$h = $b = strlen($basic);
while ($h < strlen($input)) {
$m = 0x7FFFFFFF;
foreach ($chars as $char) {
$c = ord($char);
if ($c >= $n && $c < $m) {
$m = $c;
}
}
$delta += ($m - $n) * ($h + 1);
$n = $m;
foreach ($chars as $char) {
$c = ord($char);
if ($c < $n) {
$delta++;
} elseif ($c === $n) {
$q = $delta;
for ($k = 36;; $k += 36) {
$t = ($k <= $bias) ? 1 : ($k >= $bias + 26) ? 26 : $k - $bias;
if ($q < $t) break;
$output .= chr($t + ($q - $t) % (36 - $t) + 97);
$q = floor(($q - $t) / (36 - $t));
}
$output .= chr($q + 97);
$bias = adapt($delta, $h + 1, $h === $b);
$delta = 0;
$h++;
}
}
$delta++;
$n++;
}
return $output;
}
function adapt($delta, $numpoints, $firsttime) {
$delta = $firsttime ? floor($delta / 700) : floor($delta / 2);
$delta += floor($delta / $numpoints);
$k = 0;
while ($delta > 455) {
$delta = floor($delta / 35);
$k += 36;
}
return $k + floor(36 * $delta / ($delta + 38));
}
Punycode 解码函数
function punycode_decode($input) {
$output = '';
$n = 128;
$i = 0;
$bias = 72;
$basic = preg_replace('/-[^-]*$/', '', $input);
$basic = preg_replace('/-$/', '', $basic);
if ($basic !== '') {
$output = $basic;
$i = strlen($basic);
}
$pos = strrpos($input, '-');
if ($pos !== false) {
$input = substr($input, $pos + 1);
}
while (!empty($input)) {
$oldi = $i;
$w = 1;
for ($k = 36;; $k += 36) {
$digit = ord($input[0]) - 97;
$input = substr($input, 1);
$i += $digit * $w;
$t = ($k <= $bias) ? 1 : ($k >= $bias + 26) ? 26 : $k - $bias;
if ($digit < $t) break;
$w *= (36 - $t);
}
$bias = adapt($i - $oldi, strlen($output) + 1, $oldi === 0);
$n += floor($i / (strlen($output) + 1));
$i %= (strlen($output) + 1);
$output = substr($output, 0, $i) . chr($n) . substr($output, $i);
$i++;
}
return $output;
}
注意事项
intl扩展是官方推荐的方式,支持最新的 IDNA 标准(如 UTS #46)。- 手动实现的 Punycode 函数可能不完全符合标准,仅适用于简单场景。
- 对于生产环境,建议优先使用
idn_to_ascii和idn_to_utf8。






