giggsey/libphonenumber-for-php
PHP port of Google’s libphonenumber for parsing, formatting, validating, and storing international phone numbers. Supports geocoding, carrier and timezone mapping, plus short-number info. Composer install; requires mbstring.
Installation:
composer require giggsey/libphonenumber-for-php
Ensure mbstring extension is enabled in your php.ini.
First Use Case: Parse and validate a phone number:
use libphonenumber\PhoneNumberUtil;
$phoneUtil = PhoneNumberUtil::getInstance();
$number = $phoneUtil->parse('+1 650 253 0000', null);
$isValid = $phoneUtil->isValidNumber($number);
Key Classes to Explore:
PhoneNumberUtil (core parsing/formatting)PhoneNumberOfflineGeocoder (location lookup)PhoneNumberToCarrierMapper (carrier identification)Validation & Parsing:
$phoneUtil = PhoneNumberUtil::getInstance();
try {
$number = $phoneUtil->parse($rawInput, $regionCode);
if ($phoneUtil->isValidNumber($number)) {
// Proceed with valid number
}
} catch (NumberParseException $e) {
// Handle invalid format
}
Formatting for Display:
// E164 (international standard): +442071838750
$e164 = $phoneUtil->format($number, PhoneNumberFormat::E164);
// National format (e.g., "020 7183 8750" for UK)
$national = $phoneUtil->format($number, PhoneNumberFormat::NATIONAL);
// International format (e.g., "+44 20 7183 8750")
$international = $phoneUtil->format($number, PhoneNumberFormat::INTERNATIONAL);
Geocoding:
$geocoder = PhoneNumberOfflineGeocoder::getInstance();
$location = $geocoder->getDescriptionForNumber($number, 'en_US');
Carrier Lookup:
$carrierMapper = PhoneNumberToCarrierMapper::getInstance();
$carrierName = $carrierMapper->getNameForNumber($number, 'en');
Service Provider:
Register the package in AppServiceProvider:
public function boot()
{
$this->app->singleton(PhoneNumberUtil::class, function () {
return PhoneNumberUtil::getInstance();
});
}
Request Validation: Use in Laravel Form Requests:
use libphonenumber\PhoneNumberUtil;
use libphonenumber\NumberParseException;
public function rules()
{
return [
'phone' => 'required|phone_number'
];
}
public function validatePhone($attribute, $value, $fail)
{
$phoneUtil = app(PhoneNumberUtil::class);
try {
$number = $phoneUtil->parse($value);
if (!$phoneUtil->isValidNumber($number)) {
$fail('The :attribute is invalid.');
}
} catch (NumberParseException $e) {
$fail('The :attribute is invalid.');
}
}
Model Casting: Cast phone numbers in Eloquent models:
use libphonenumber\PhoneNumberUtil;
use libphonenumber\PhoneNumberFormat;
protected $casts = [
'phone' => PhoneNumberCast::class,
];
// Custom Cast Class
class PhoneNumberCast implements AttributeCast
{
public function get($model, string $key, $value, array $attributes)
{
return $value;
}
public function set($model, string $key, $value, array $attributes)
{
$phoneUtil = app(PhoneNumberUtil::class);
$number = $phoneUtil->parse($value);
return $phoneUtil->format($number, PhoneNumberFormat::E164);
}
}
API Responses: Format numbers consistently in API responses:
return response()->json([
'phone' => $phoneUtil->format($user->phoneNumber, PhoneNumberFormat::E164)
]);
Region Code Ambiguity:
+44...) may yield unexpected results if the number could belong to multiple regions.parse('020...', 'GB')).Invalid Numbers:
parse() throws NumberParseException for malformed input, but isValidNumber() checks logical validity (e.g., does the number exist for the region?).try {
$number = $phoneUtil->parse($input);
if (!$phoneUtil->isValidNumber($number)) {
throw new \InvalidArgumentException('Invalid phone number.');
}
} catch (NumberParseException $e) {
throw new \InvalidArgumentException('Malformed phone number.');
}
Geocoding Limitations:
PhoneNumberOfflineGeocoder for basic city-level resolution, then supplement with external services for street addresses.Carrier Mapping:
PhoneNumberToCarrierMapper::getInstance()->isSupportedRegion($regionCode).Performance:
$phoneUtil = PhoneNumberUtil::getInstance(); // Load once at app startup
Inspect Parsed Numbers:
Use var_dump($number) to debug PhoneNumber objects. Key properties:
countryCode: E.g., 44 for UK.nationalNumber: E.g., 2071838750 for 020 7183 8750.extension: If present, e.g., x123.Common Exceptions:
NumberParseException: Invalid format (e.g., parse('abc', 'GB')).NumberFormatException: Invalid format specifier (e.g., format($number, 999)).Locale-Specific Issues:
en_US vs. de_DE). Test with target locales early.Custom Formatting:
Extend PhoneNumberUtil for project-specific formats:
class CustomPhoneNumberUtil extends PhoneNumberUtil
{
public function formatCustom($number)
{
return '(' . substr($this->format($number, PhoneNumberFormat::NATIONAL), 0, 4) . ') ' .
substr($this->format($number, PhoneNumberFormat::NATIONAL), 4);
}
}
Validation Rules: Create reusable validation logic:
class PhoneNumberValidator
{
public static function validate($input, $region = null)
{
$phoneUtil = PhoneNumberUtil::getInstance();
try {
$number = $phoneUtil->parse($input, $region);
return $phoneUtil->isValidNumber($number);
} catch (NumberParseException) {
return false;
}
}
}
Event Listeners: Trigger actions on phone number changes (e.g., in Laravel models):
protected static function booted()
{
static::updating(function ($model) {
if ($model->isDirty('phone')) {
$phoneUtil = app(PhoneNumberUtil::class);
$model->phone = $phoneUtil->format(
$phoneUtil->parse($model->phone),
PhoneNumberFormat::E164
);
}
});
}
Testing: Mock the singleton for unit tests:
$mockUtil = $this->createMock(PhoneNumberUtil::class);
$mockUtil->method('parse')->willReturn($validNumber);
$mockUtil->method('isValidNumber')->willReturn(true);
$this->app->instance(PhoneNumberUtil::class, $mockUtil);
Metadata Updates:
libphonenumber. To update:
composer run build
**Memory Usage
How can I help you explore Laravel packages today?