使用 libphonenumber 在 PHP 中构建电话号码工具类:国家、语言和时区检测
简介
在构建全球化应用时,电话号码通常是最可靠的用户数据。与电子邮件地址或用户名不同,电话号码包含固有的地理信息,可以帮助个性化用户体验。从自动选择正确的通知语言到在适当的本地时间安排消息发送,从电话号码中提取元数据是非常有价值的。
本指南演示如何使用 Google 的 libphonenumber 库(通过 PHP 版本)构建一个 PhoneUtil 类,从国际电话号码中提取国家代码、推断首选语言和检测时区。
前置条件
- PHP 8.1 或更高版本
- Composer 包管理器
- PHP 类和异常处理的基础知识
安装 libphonenumber-for-php
通过 Composer 安装库:
composer require giggsey/libphonenumber-for-php精简版可用
如果你只需要基本的解析和格式化功能,不需要地理编码或运营商信息,可以使用 giggsey/libphonenumber-for-php-lite 获得更小的安装体积。
构建 PhoneUtil 类
让我们创建一个工具类,提供三个核心方法:国家代码检测、语言推断和时区解析。
基本结构
<?php
namespace App\Utils;
use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;
class PhoneUtil
{
// 方法将在这里添加
}获取国家代码
第一个方法从电话号码中提取 ISO 3166-1 alpha-2 国家代码(如 "US"、"BR"、"MX"):
public static function getCountryCode(string $phone_number): ?string
{
try {
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}");
return $phoneUtil->getRegionCodeForNumber($phoneObj);
} catch (Throwable $e) {
// 记录或报告错误
return null;
}
}电话号码格式
此实现期望电话号码不包含前导 + 符号。方法在解析时会自动添加。请根据你的应用存储电话号码的方式进行调整。
推断用户语言
基于国家代码,我们可以对用户的首选语言做出合理推断。这对于发送本地化通知特别有用:
public static function getCountryLanguage(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
if (! $country_code) {
return null;
}
return match ($country_code) {
// 西班牙语国家
'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
'CR', 'PA', 'UY' => 'es',
// 葡萄牙语
'BR' => 'pt_BR',
// 中文
'CN', 'TW', 'HK', 'SG' => 'zh_CN',
// 印地语
'IN' => 'hi',
// 默认英语
default => 'en',
};
} catch (Throwable $e) {
return null;
}
}扩展语言支持
上述映射涵盖了主要语言。根据你的应用支持的语言环境进行扩展。可以考虑添加法国(FR => fr)、德国(DE => de)或日本(JP => ja)等国家。
检测时区
时区检测对于在适当时间安排用户通信至关重要。libphonenumber 库提供了 PhoneNumberToTimeZonesMapper 类来实现这一功能:
public static function getTimezone(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}", $country_code);
$timezoneMapper = PhoneNumberToTimeZonesMapper::getInstance();
$timezones = $timezoneMapper->getTimeZonesForNumber($phoneObj);
$timezone = $timezones[0] ?? null;
if (! $timezone) {
return null;
}
// 处理未知时区的回退
if ($timezone === 'Etc/Unknown') {
return match ($country_code) {
'MX' => 'America/Mexico_City',
'BR' => 'America/Sao_Paulo',
'IN' => 'Asia/Kolkata',
default => null,
};
}
return $timezone;
} catch (Throwable $e) {
return null;
}
}处理已弃用的时区
某些电话号码可能返回已弃用的时区标识符。将它们映射到现代等效标识符是一个好习惯:
public static function getTimezone(string $phone_number): ?string
{
try {
// ... 前面的代码 ...
// 将已弃用的时区映射到现代等效时区
$deprecatedTimezones = [
'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
'America/Catamarca' => 'America/Argentina/Catamarca',
'America/Cordoba' => 'America/Argentina/Cordoba',
'America/Jujuy' => 'America/Argentina/Jujuy',
'America/Mendoza' => 'America/Argentina/Mendoza',
];
if (isset($deprecatedTimezones[$timezone])) {
return $deprecatedTimezones[$timezone];
}
return $timezone;
} catch (Throwable $e) {
return null;
}
}完整实现
这是组合所有方法的完整 PhoneUtil 类:
<?php
namespace App\Utils;
use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;
class PhoneUtil
{
public static function getCountryCode(string $phone_number): ?string
{
try {
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}");
return $phoneUtil->getRegionCodeForNumber($phoneObj);
} catch (Throwable $e) {
// 考虑记录日志: Errors::reportThrowable($e);
return null;
}
}
public static function getCountryLanguage(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
if (! $country_code) {
return null;
}
return match ($country_code) {
'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
'CR', 'PA', 'UY' => 'es',
'BR' => 'pt_BR',
'CN', 'TW', 'HK', 'SG' => 'zh_CN',
'IN' => 'hi',
default => 'en',
};
} catch (Throwable $e) {
return null;
}
}
public static function getTimezone(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}", $country_code);
$timezoneMapper = PhoneNumberToTimeZonesMapper::getInstance();
$timezones = $timezoneMapper->getTimeZonesForNumber($phoneObj);
$timezone = $timezones[0] ?? null;
if (! $timezone) {
return null;
}
if ($timezone === 'Etc/Unknown') {
return match ($country_code) {
'MX' => 'America/Mexico_City',
'BR' => 'America/Sao_Paulo',
'IN' => 'Asia/Kolkata',
default => null,
};
}
$deprecatedTimezones = [
'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
'America/Catamarca' => 'America/Argentina/Catamarca',
'America/Cordoba' => 'America/Argentina/Cordoba',
'America/Jujuy' => 'America/Argentina/Jujuy',
'America/Mendoza' => 'America/Argentina/Mendoza',
];
return $deprecatedTimezones[$timezone] ?? $timezone;
} catch (Throwable $e) {
return null;
}
}
}使用示例
基本用法
use App\Utils\PhoneUtil;
// 巴西电话号码
$phone = '5511999998888';
$country = PhoneUtil::getCountryCode($phone); // "BR"
$language = PhoneUtil::getCountryLanguage($phone); // "pt_BR"
$timezone = PhoneUtil::getTimezone($phone); // "America/Sao_Paulo"实际应用:本地化通知
public function sendNotification(User $user, string $message): void
{
$language = PhoneUtil::getCountryLanguage($user->phone);
$timezone = PhoneUtil::getTimezone($user->phone);
// 设置翻译的语言环境
app()->setLocale($language ?? 'en');
// 在适当的本地时间安排发送
$sendAt = now($timezone)->setTime(10, 0);
Notification::send($user, new UserNotification(
message: __($message),
scheduledAt: $sendAt
));
}调试电话号码
libphonenumber 项目提供了一个在线演示,用于测试电话号码解析:
https://libphonenumber.appspot.com
在报告问题之前,使用此工具验证预期行为。
故障排除
返回未知时区
对于某些移动号码,特别是在巴西、墨西哥或印度等大国,库可能返回 Etc/Unknown。这发生在号码没有提供足够的地理特异性时。如上所示,使用国家级回退来处理这种情况。
解析失败
解析失败的常见原因:
- 电话号码格式无效
- 国家代码前缀缺失或不正确
- 输入中包含非数字字符
在处理之前始终对电话号码进行清理:
$phone = preg_replace('/[^0-9]/', '', $phone);结论
使用 libphonenumber 构建电话号码工具为国际化应用提供了可靠的基础。通过提取国家代码、推断语言和检测时区,你可以提供尊重用户地理环境的个性化体验。
关键要点:
- 使用
PhoneNumberUtil进行解析和国家检测 - 将国家代码映射到你支持的语言环境以进行语言推断
- 使用合理的国家级默认值处理
Etc/Unknown时区 - 将已弃用的时区标识符映射到其现代等效项
对于有严格要求的应用,考虑允许用户在其偏好设置中覆盖推断的值,同时使用基于电话的检测作为智能默认值。

