symfony/polyfill-intl-idn
Provides polyfills for the Intl IDN functions idn_to_ascii() and idn_to_utf8(), enabling Internationalized Domain Name conversion on PHP installations without the intl extension. Part of Symfony’s Polyfill suite, MIT licensed.
Installation: Add the package via Composer:
composer require symfony/polyfill-intl-idn
No additional configuration is required.
First Use Case: Convert an internationalized domain name (IDN) to ASCII (Punycode) for storage or DNS resolution:
use Symfony\Component\Polyfill\Intl\Idn\IdnPolyfill;
$asciiDomain = IdnPolyfill::toAscii('例子.测试'); // Returns 'xn--fsq.xn--0zwm56d'
Or convert ASCII back to UTF-8 for display:
$utf8Domain = IdnPolyfill::toUtf8('xn--fsq.xn--0zwm56d'); // Returns '例子.测试'
Where to Look First:
IdnPolyfill.php for implementation details.Domain Normalization:
Use toAscii() for storing domains in databases or APIs where ASCII is required:
$userInput = '北京.测试';
$normalized = IdnPolyfill::toAscii($userInput);
// Store $normalized in DB or use in DNS queries
Email Validation: Validate internationalized email addresses by extracting and converting the domain:
$email = '用户@例子.测试';
$domain = explode('@', $email)[1];
$asciiDomain = IdnPolyfill::toAscii($domain);
// Proceed with validation logic
URL Generation: Convert IDNs to ASCII for URLs to ensure compatibility with legacy systems:
$url = 'https://例子.测试';
$asciiUrl = str_replace('例子.测试', IdnPolyfill::toAscii('例子.测试'), $url);
// Use $asciiUrl in redirects or API calls
Laravel Integration:
Create a helper function in app/Helpers/IdnHelper.php:
use Symfony\Component\Polyfill\Intl\Idn\IdnPolyfill;
if (!function_exists('idn_to_ascii')) {
function idn_to_ascii($domain) {
return IdnPolyfill::toAscii($domain);
}
}
if (!function_exists('idn_to_utf8')) {
function idn_to_utf8($domain) {
return IdnPolyfill::toUtf8($domain);
}
}
Register the helper in composer.json autoload:
"autoload": {
"files": ["app/Helpers/IdnHelper.php"]
}
Caching: Cache frequent conversions to mitigate performance overhead:
$cacheKey = 'idn_ascii_' . md5($domain);
$asciiDomain = cache()->remember($cacheKey, now()->addHours(1), function () use ($domain) {
return IdnPolyfill::toAscii($domain);
});
User Input Handling:
public function handle($request, Closure $next) {
if ($request->has('domain')) {
$request->merge([
'domain_ascii' => IdnPolyfill::toAscii($request->input('domain'))
]);
}
return $next($request);
}
API Responses:
return response()->json([
'domain' => IdnPolyfill::toUtf8($storedAsciiDomain)
]);
Testing:
use Symfony\Component\Polyfill\Intl\Idn\IdnPolyfill;
beforeEach(function () {
$this->mock(IdnPolyfill::class)->shouldReceive('toAscii')
->andReturn('xn--fsq.xn--0zwm56d');
});
Laravel Validation: Extend Laravel’s validation rules to support IDNs:
use Illuminate\Validation\Rule;
Rule::macro('valid_idn_domain', function ($format = null) {
return function ($attribute, $value, $parameters) {
$ascii = IdnPolyfill::toAscii($value);
return preg_match('/^([a-z\d-]+\.)+[a-z\d-]{2,}$/i', $ascii);
};
});
Usage:
'domain' => ['required', 'valid_idn_domain'],
Database Storage: Store domains in ASCII format in the database and convert back to UTF-8 when retrieving:
// Store
$user->domain = IdnPolyfill::toAscii($request->domain);
$user->save();
// Retrieve
$domain = IdnPolyfill::toUtf8($user->domain);
DNS and API Calls: Use ASCII domains for DNS lookups or API calls to ensure compatibility:
$asciiDomain = IdnPolyfill::toAscii($userInputDomain);
$dnsResult = dns_get_record($asciiDomain);
Localization: Combine with Laravel’s localization features for multilingual support:
$locale = app()->getLocale();
$translatedDomain = __("domains.$locale.example");
$asciiDomain = IdnPolyfill::toAscii($translatedDomain);
Invalid Input Handling:
The polyfill may return false for invalid input (e.g., malformed Unicode). Always validate:
$asciiDomain = IdnPolyfill::toAscii($domain);
if ($asciiDomain === false) {
throw new \InvalidArgumentException("Invalid IDN: $domain");
}
Performance Overhead:
Polyfills are slower than native Intl functions. Benchmark critical paths and cache results:
// Benchmark example
$start = microtime(true);
IdnPolyfill::toAscii('例子.测试');
$time = microtime(true) - $start;
// Log or alert if $time > 0.01 (10ms threshold)
Behavioral Differences:
The polyfill may handle edge cases differently than the native Intl extension. Test with:
例子.测试.例子).例子..测试).Caching Issues:
Avoid caching toUtf8() results for user-generated content, as they may change over time (e.g., domain updates).
PHP Version Quirks:
Unexpected Output:
If toAscii() or toUtf8() returns unexpected results, verify the input:
$domain = '例子.测试';
echo "Input: " . bin2hex($domain) . "\n"; // Debug raw bytes
echo "Output: " . IdnPolyfill::toAscii($domain) . "\n";
False Returns:
Check for false returns and handle gracefully:
$result = IdnPolyfill::toAscii($domain);
if ($result === false) {
// Log the input for debugging
\Log::error("IDN conversion failed for: $domain", ['input' => bin2hex($domain)]);
}
Memory Leaks: The polyfill is stateless, but complex conversions may consume memory. Monitor with:
composer require --dev ext-meminfo
Then in code:
$memoryBefore = memory_get_usage();
$result = IdnPolyfill::toAscii($domain);
$memoryAfter = memory_get_usage();
if ($memoryAfter - $memoryBefore > 1024 * 1024) { // >1MB
\Log::warning("High memory usage in IDN conversion");
}
How can I help you explore Laravel packages today?