Skip to content

Utilitários de Números de Telefone com libphonenumber em PHP: Detecção de País, Idioma e Fuso Horário

Introdução

Ao desenvolver aplicações globais, os números de telefone são frequentemente os dados de usuário mais confiáveis disponíveis. Diferente de endereços de e-mail ou nomes de usuário, números de telefone carregam informações geográficas inerentes que podem ajudar a personalizar experiências. Desde selecionar automaticamente o idioma correto para notificações até agendar mensagens em horários locais apropriados, extrair metadados de números de telefone é inestimável.

Este guia demonstra como construir uma classe PhoneUtil usando a biblioteca libphonenumber do Google (via a versão PHP) para extrair códigos de país, inferir idiomas preferidos e detectar fusos horários de números de telefone internacionais.

Pré-requisitos

  • PHP 8.1 ou superior
  • Gerenciador de pacotes Composer
  • Conhecimento básico de classes PHP e tratamento de exceções

Instalando libphonenumber-for-php

Instale a biblioteca via Composer:

bash
composer require giggsey/libphonenumber-for-php

Versão Lite Disponível

Se você precisa apenas de parsing e formatação básicos sem geocodificação ou informações de operadora, pode usar giggsey/libphonenumber-for-php-lite para uma instalação mais leve.

Construindo a Classe PhoneUtil

Vamos criar uma classe utilitária que fornece três métodos principais: detecção de código de país, inferência de idioma e resolução de fuso horário.

Estrutura Básica

php
<?php

namespace App\Utils;

use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;

class PhoneUtil
{
    // Os métodos virão aqui
}

Obtendo o Código do País

O primeiro método extrai o código de país ISO 3166-1 alfa-2 (ex: "US", "BR", "MX") de um número de telefone:

php
public static function getCountryCode(string $phone_number): ?string
{
    try {
        $phoneUtil = PhoneNumberUtil::getInstance();
        $phoneObj = $phoneUtil->parse("+{$phone_number}");

        return $phoneUtil->getRegionCodeForNumber($phoneObj);
    } catch (Throwable $e) {
        // Registre ou reporte o erro
        return null;
    }
}

Formato do Número de Telefone

Esta implementação espera números de telefone sem o sinal + inicial. O método adiciona-o durante o parsing. Ajuste conforme a forma que sua aplicação armazena os números.

Inferindo o Idioma do Usuário

Com base no código do país, podemos fazer suposições educadas sobre o idioma preferido do usuário. Isso é particularmente útil para enviar notificações localizadas:

php
public static function getCountryLanguage(string $phone_number): ?string
{
    try {
        $country_code = self::getCountryCode($phone_number);

        if (! $country_code) {
            return null;
        }

        return match ($country_code) {
            // Países de língua espanhola
            'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
            'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
            'CR', 'PA', 'UY' => 'es',

            // Português
            'BR' => 'pt_BR',

            // Chinês
            'CN', 'TW', 'HK', 'SG' => 'zh_CN',

            // Hindi
            'IN' => 'hi',

            // Padrão para inglês
            default => 'en',
        };
    } catch (Throwable $e) {
        return null;
    }
}

Expandindo Suporte a Idiomas

O mapeamento acima cobre os principais idiomas. Expanda-o com base nos locales suportados pela sua aplicação. Considere países como França (FR => fr), Alemanha (DE => de) ou Japão (JP => ja) conforme necessário.

Detectando Fuso Horário

A detecção de fuso horário é crucial para agendar comunicações com usuários em horários apropriados. A biblioteca libphonenumber fornece a classe PhoneNumberToTimeZonesMapper para esse propósito:

php
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;
        }

        // Trata fallbacks de fuso horário desconhecido
        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;
    }
}

Tratando Fusos Horários Obsoletos

Alguns números de telefone podem retornar identificadores de fuso horário obsoletos. É uma boa prática mapeá-los para seus equivalentes modernos:

php
public static function getTimezone(string $phone_number): ?string
{
    try {
        // ... código anterior ...

        // Mapeia fusos horários obsoletos para equivalentes modernos
        $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;
    }
}

Implementação Completa

Aqui está a classe PhoneUtil completa combinando todos os métodos:

php
<?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) {
            // Considere registrar: 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;
        }
    }
}

Exemplos de Uso

Uso Básico

php
use App\Utils\PhoneUtil;

// Número de telefone brasileiro
$phone = '5511999998888';

$country = PhoneUtil::getCountryCode($phone);      // "BR"
$language = PhoneUtil::getCountryLanguage($phone); // "pt_BR"
$timezone = PhoneUtil::getTimezone($phone);        // "America/Sao_Paulo"

Aplicação Prática: Notificações Localizadas

php
public function sendNotification(User $user, string $message): void
{
    $language = PhoneUtil::getCountryLanguage($user->phone);
    $timezone = PhoneUtil::getTimezone($user->phone);

    // Define o locale para tradução
    app()->setLocale($language ?? 'en');

    // Agenda no horário local apropriado
    $sendAt = now($timezone)->setTime(10, 0);

    Notification::send($user, new UserNotification(
        message: __($message),
        scheduledAt: $sendAt
    ));
}

Depurando Números de Telefone

O projeto libphonenumber fornece uma demonstração online para testar o parsing de números de telefone:

https://libphonenumber.appspot.com

Use esta ferramenta para verificar o comportamento esperado antes de reportar problemas.

Solução de Problemas

Retornos de Fuso Horário Desconhecido

Para alguns números móveis, especialmente em países grandes como Brasil, México ou Índia, a biblioteca pode retornar Etc/Unknown. Isso acontece quando o número não fornece especificidade geográfica suficiente. Trate isso com fallbacks a nível de país, conforme mostrado acima.

Falhas de Parsing

Causas comuns de falhas de parsing:

  • Formato inválido de número de telefone
  • Prefixo de código de país ausente ou incorreto
  • Caracteres não numéricos na entrada

Sempre sanitize os números de telefone antes de processar:

php
$phone = preg_replace('/[^0-9]/', '', $phone);

Conclusão

Construir utilitários de números de telefone com libphonenumber fornece uma base confiável para aplicações internacionalizadas. Ao extrair códigos de país, inferir idiomas e detectar fusos horários, você pode entregar experiências personalizadas que respeitam os contextos geográficos dos usuários.

Os pontos principais são:

  • Use PhoneNumberUtil para parsing e detecção de país
  • Mapeie códigos de país para seus locales suportados para inferência de idioma
  • Trate fusos horários Etc/Unknown com defaults sensatos a nível de país
  • Mapeie identificadores de fuso horário obsoletos para seus equivalentes modernos

Para aplicações com requisitos rigorosos, considere permitir que os usuários sobrescrevam os valores inferidos em suas preferências, usando a detecção baseada em telefone como padrões inteligentes.