wal3fo/phone-country

Resolve country code from phone number for Laravel, with AI-powered normalization, disambiguation, fraud detection, and metadata enrichment.

Maintainers

Package info

github.com/wal3fo/Phone-Country-Resolver

pkg:composer/wal3fo/phone-country

Fund package maintenance!

wal3fo

Statistics

Installs: 15

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.0.0 2026-04-11 20:25 UTC

This package is auto-updated.

Last update: 2026-05-22 00:09:38 UTC


README

phone-country-resolver

Packagist Version Total Downloads PHP Laravel License


Resolve ISO 3166-1 alpha-2 country codes from phone numbers.
Rich metadata Β· Flag & geography Β· Number formatting Β· HTML card output Β· Zero dependencies.


composer require wal3fo/phone-country

Features

Feature Method Description
πŸ” Country resolution resolveCountryCode() Returns the ISO alpha-2 code from any phone format
🌐 Rich analysis analyze() Full country metadata, number type, formatting, and more
🏳️ Flag & geography analyze() Emoji flag, region, continent, and capital city
πŸ’± Currency & language analyze() ISO 4217 currency, currency name, and primary language
πŸ• Timezone analyze() IANA timezone for 180+ countries
πŸ“ Formatting analyze() National and international number formats
πŸ–ΌοΈ HTML card toHtml() Self-contained, styled HTML card β€” no external dependencies
βœ… Validation rule PhoneCountryRule Laravel validation rule with allowlist and strict mode

Quick start

use Wal3fo\PhoneCountry\PhoneCountryService;

$analysis = PhoneCountryService::analyze('+212612345678');

// ── Country & identity ────────────────────────────────────
$analysis->countryCode;        // 'MA'
$analysis->countryName;        // 'Morocco'
$analysis->flag;               // 'πŸ‡²πŸ‡¦'
$analysis->dialingCode;        // '212'
$analysis->e164;               // '+212612345678'

// ── Geography ─────────────────────────────────────────────
$analysis->region;             // 'Northern Africa'
$analysis->continent;          // 'Africa'
$analysis->capital;            // 'Rabat'

// ── Cultural metadata ─────────────────────────────────────
$analysis->timezone;           // 'Africa/Casablanca'
$analysis->currency;           // 'MAD'
$analysis->currencyName;       // 'Moroccan Dirham'
$analysis->language;           // 'Arabic'

// ── Number analysis ───────────────────────────────────────
$analysis->nationalNumber;     // '612345678'
$analysis->numberType;         // 'MOBILE'
$analysis->isValid;            // true
$analysis->isPossible;         // true
$analysis->digitCount;         // 9

// ── Formatting ────────────────────────────────────────────
$analysis->formatNational;        // '61 23 45 67 8'
$analysis->formatInternational;   // '+212 612 345 678'

// ── Input metadata ────────────────────────────────────────
$analysis->raw;                // '+212612345678'
$analysis->normalized;         // '212612345678'
$analysis->inputFormat;        // 'E164'
$analysis->usedFallback;       // false
$analysis->resolvedAt;         // '2025-01-01T00:00:00+00:00'

// ── Output helpers ────────────────────────────────────────
$analysis->explanation;        // HTML <ul> summary card
$analysis->toArray();          // full associative array
$analysis->toJson();           // JSON string
$analysis->toHtml();           // self-contained HTML card

Core methods

resolveCountryCode()

Resolves a phone number and returns the ISO 3166-1 alpha-2 country code.

PhoneCountryService::resolveCountryCode('+212612345678');       // 'MA'
PhoneCountryService::resolveCountryCode('00212612345678');      // 'MA'
PhoneCountryService::resolveCountryCode('0612345678', 'MA');    // 'MA'  ← local fallback
PhoneCountryService::resolveCountryCode('0612345678');          // 'XX'  ← unresolvable

Returns 'XX' when the number cannot be resolved to any country.

analyze()

Analyzes a phone number and returns a rich PhoneAnalysis value object.

$analysis = PhoneCountryService::analyze('+33612345678');

$analysis->countryCode;   // 'FR'
$analysis->countryName;   // 'France'
$analysis->flag;          // 'πŸ‡«πŸ‡·'
$analysis->timezone;      // 'Europe/Paris'
$analysis->currency;      // 'EUR'
$analysis->language;      // 'French'
$analysis->numberType;    // 'MOBILE'
$analysis->isValid;       // true
$analysis->toHtml();      // styled HTML card

With a local number and fallback country:

$analysis = PhoneCountryService::analyze('0612345678', 'MA');
$analysis->countryCode;   // 'MA'
$analysis->e164;          // '+212612345678'
$analysis->inputFormat;   // 'LOCAL'
$analysis->usedFallback;  // true

Validation rule

use Wal3fo\PhoneCountry\Rules\PhoneCountryRule;

// Any valid international number
'phone' => ['required', new PhoneCountryRule()]

// With Morocco as local fallback
'phone' => ['required', new PhoneCountryRule('MA')]

// Restrict to a country allowlist
'phone' => ['required', new PhoneCountryRule(['MA', 'FR', 'ES'])]

// Strict mode β€” reject local 0xxx format
'phone' => ['required', new PhoneCountryRule('MA', strict: true)]

Example in a Form Request:

use Wal3fo\PhoneCountry\Rules\PhoneCountryRule;

class RegisterRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name'  => ['required', 'string'],
            'phone' => ['required', 'string', new PhoneCountryRule('MA')],
        ];
    }
}

Dependency injection

class UserProfileController extends Controller
{
    public function __construct(
        protected PhoneCountryService $phoneService
    ) {}

    public function update(Request $request)
    {
        $analysis = $this->phoneService->analyze($request->phone_number);

        $user->update([
            'phone'        => $analysis->e164,
            'country_code' => $analysis->countryCode,
        ]);
    }
}

analyze() β€” rich pipeline

🌐 Country metadata

Every resolved number returns full geographic and cultural data:

$a = PhoneCountryService::analyze('+966512345678');  // Saudi Arabia

$a->countryCode;    // 'SA'
$a->countryName;    // 'Saudi Arabia'
$a->flag;           // 'πŸ‡ΈπŸ‡¦'
$a->region;         // 'Western Asia'
$a->continent;      // 'Asia'
$a->capital;        // 'Riyadh'
$a->currency;       // 'SAR'
$a->currencyName;   // 'Saudi Riyal'
$a->language;       // 'Arabic'
$a->timezone;       // 'Asia/Riyadh'

Covers 180+ countries with IANA timezone Β· ISO 4217 currency Β· capital city Β· world region.

πŸ“ Number formatting

analyze() produces both national and international formatted strings:

$a = PhoneCountryService::analyze('+212612345678');
$a->formatNational;        // '61 23 45 67 8'
$a->formatInternational;   // '+212 612 345 678'
$a->nationalNumber;        // '612345678'
$a->digitCount;            // 9

$b = PhoneCountryService::analyze('+33612345678');
$b->formatNational;        // '61 23 45 67'
$b->formatInternational;   // '+33 612 345 678'

πŸ”’ Number type & validity

$a = PhoneCountryService::analyze('+212612345678');
$a->numberType;   // 'MOBILE'
$a->isValid;      // true   β€” passes length check for MA (exactly 9 digits)
$a->isPossible;   // true   β€” within Β±1 of expected range

$b = PhoneCountryService::analyze('+18005551234');
$b->numberType;   // 'TOLL_FREE'

$c = PhoneCountryService::analyze('+19005551234');
$c->numberType;   // 'PREMIUM'

Number types: MOBILE Β· FIXED_LINE Β· TOLL_FREE Β· PREMIUM Β· UNKNOWN

πŸ–ΌοΈ HTML card output

toHtml() returns a fully self-contained, styled card with no external dependencies:

$html = PhoneCountryService::analyze('+212612345678')->toHtml();
// Returns a <div class="pcr-card"> with inline CSS, flag, badges, and a data table

Ideal for debug views, Blade partials, or API responses that render in a browser.

The explanation magic property returns an HTML <ul> list summary:

echo PhoneCountryService::analyze('+33612345678')->explanation;
// <ul>
//   <li><strong>Number:</strong> +33612345678</li>
//   <li><strong>Country:</strong> πŸ‡«πŸ‡· France (+33)</li>
//   ...
// </ul>

πŸ”„ Input format detection

analyze() recognizes and normalizes multiple input formats:

PhoneCountryService::analyze('+212612345678')->inputFormat;    // 'E164'
PhoneCountryService::analyze('00212612345678')->inputFormat;   // 'DOUBLE_ZERO'
PhoneCountryService::analyze('212612345678')->inputFormat;     // 'NUMERIC'
PhoneCountryService::analyze('0612345678', 'MA')->inputFormat; // 'LOCAL'

PhoneAnalysis reference

Properties

Property Type Description
raw string Original input as-is
normalized string Cleaned digits (no +, spaces, or dashes)
e164 string Canonical E.164 format, e.g. +212612345678
nationalNumber string Number without the country calling code
dialingCode string Numeric calling code, e.g. 212
countryCode string ISO 3166-1 alpha-2, e.g. MA
countryName string Full English name, e.g. Morocco
flag string Emoji flag, e.g. πŸ‡²πŸ‡¦
region string World region, e.g. Northern Africa
continent string Continent, e.g. Africa
capital string Capital city, e.g. Rabat
currency string ISO 4217 code, e.g. MAD
currencyName string Currency name, e.g. Moroccan Dirham
language string Primary language, e.g. Arabic
timezone string IANA timezone, e.g. Africa/Casablanca
numberType string MOBILE / FIXED_LINE / TOLL_FREE / PREMIUM / UNKNOWN
isValid bool Passes basic length check for the country
isPossible bool Within Β±1 digit of expected length
digitCount int Total digit count of nationalNumber
formatNational string National format, e.g. 06 12 34 56 78
formatInternational string International format, e.g. +212 612 345 678
inputFormat string E164 / DOUBLE_ZERO / NUMERIC / LOCAL
usedFallback bool true if localCountryCode fallback was applied
resolvedAt string ISO 8601 UTC timestamp of resolution

Methods & magic properties

Name Description
toArray() Full associative array β€” ready for JSON responses
toJson(int $flags) JSON string (default: JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)
toHtml() Self-contained HTML card with inline CSS
toUl() HTML <ul> summary list
$analysis->explanation Magic alias for toUl()

Examples

Input Country Name Format
+212612345678 MA Morocco E164
00212612345678 MA Morocco DOUBLE_ZERO
212612345678 MA Morocco NUMERIC
0612345678 + 'MA' MA Morocco LOCAL
+33612345678 FR France E164
+18005551234 US United States E164 (toll-free)
+966512345678 SA Saudi Arabia E164
0612345678 XX Unknown LOCAL

Installation & configuration

composer require wal3fo/phone-country

# Optionally publish the config
php artisan vendor:publish --tag=phone-country-config
// config/phone-country.php
return [
    'default_country' => env('PHONE_COUNTRY_DEFAULT', 'XX'),
    'unknown_code'    => 'XX',
];
PHONE_COUNTRY_DEFAULT=MA

Performance β€” for high-volume workloads, cache analyze() results:

$analysis = Cache::remember("phone_analysis:{$phone}", 3600, fn () =>
    PhoneCountryService::analyze($phone)
);

File structure

src/
β”œβ”€β”€ PhoneCountryService.php          ← Main service β€” resolveCountryCode() and analyze()
β”œβ”€β”€ PhoneCountryServiceProvider.php  ← Laravel service provider
β”œβ”€β”€ PhoneAnalysis.php                ← Rich value object returned by analyze()
β”œβ”€β”€ PhoneResult.php                  ← Lightweight value object
β”œβ”€β”€ CountryMetadata.php              ← Static map: flag, region, capital, currency, language, timezone
β”œβ”€β”€ Rules/
β”‚   └── PhoneCountryRule.php         ← Laravel validation rule
└── AI/
    β”œβ”€β”€ PhoneNormalizer.php          ← Input cleaning & repair
    β”œβ”€β”€ NanpDisambiguator.php        ← +1 area-code map (25+ NANP countries)
    β”œβ”€β”€ FraudDetector.php            ← Toll-free / premium / VOIP signal detection
    β”œβ”€β”€ MetadataEnricher.php         ← Extended metadata enrichment
    └── PhoneExplainer.php           ← Plain-language explanation generator
config/
└── phone-country.php

Changelog

v1.0.0

  • Initial release with resolveCountryCode() and longest-prefix matching
  • Added analyze() returning PhoneAnalysis value object
  • Added PhoneAnalysis with flag, region, continent, capital, currency name, language, timezone
  • Added national and international number formatting (formatNational, formatInternational)
  • Added isPossible and digitCount for length plausibility checks
  • Added input format detection (E164, DOUBLE_ZERO, NUMERIC, LOCAL)
  • Added usedFallback and resolvedAt metadata
  • Added toHtml() β€” self-contained HTML card with inline CSS
  • Added toJson() and toArray() for API responses
  • Added explanation magic property returning an HTML <ul> summary
  • Added CountryMetadata β€” static map for 180+ countries
  • Added PhoneCountryRule β€” Laravel validation rule with allowlist and strict mode
  • Added AI/ β€” PhoneNormalizer, NanpDisambiguator, FraudDetector, MetadataEnricher, PhoneExplainer

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/your-feature
  3. Commit your changes: git commit -m 'Add your feature'
  4. Push to the branch: git push origin feature/your-feature
  5. Open a Pull Request

License

The MIT License (MIT). See LICENSE for full details.