m-adamski/symfony-phone-number-bundle
Symfony bundle for working with phone numbers: validate, parse, and format international numbers via libphonenumber, with integrations for forms, validators, and Twig helpers. Simplifies consistent phone number handling across Symfony applications.
This package is a Symfony bundle, not a Laravel package, so the first decision is how you consume it. In a Laravel app you have two realistic paths:
composer require m-adamski/symfony-phone-number-bundle, then resolve/inject its phone number service (built on giggsey/libphonenumber-for-php) in a service class or custom validation rule. This is the lowest-friction route for most Laravel codebases.symfony/form, symfony/validator, symfony/twig-bridge) if you genuinely want the PhoneNumberType form field or the validator constraint. This pulls in a meaningful chunk of Symfony and is usually only worth it if you're already running Symfony components.Where to look first:
PhoneNumberType (form field) and its validator constraint — these are the two public entry points.default_region-style config, which drives parsing of national-format input.First use case — registration/profile phone capture:
// app/Rules/ValidPhoneNumber.php (sketch)
public function passes($attribute, $value): bool
{
try {
$number = $this->phoneUtil->parse($value, $this->defaultRegion); // e.g. 'US'
return $this->phoneUtil->isValidNumber($number);
} catch (\Throwable) {
return false;
}
}
Store the normalized E.164 string (+14155552671) and format for display at render time, not at write time.
Normalize on input, format on output. Parse with the user's/default region, immediately convert to E.164 for persistence, and keep the raw input only if you need an audit trail. Display formatting (national/international) should be a presentation concern.
Custom Laravel validation rule wrapping the parser. Prefer a dedicated Rule object over inline closures so the default region and error messaging live in one place. Pair it with a prepareForValidation() merge or a form request passedValidation() hook to normalize before it hits the database.
Form-type usage (Symfony bridge only). If you wire up PhoneNumberType, it gives you a country selector plus a number input. Bind the underlying services through a Laravel service provider so the form type's dependencies resolve. This is the clearest justification for adopting the bundle rather than the raw library.
Region-aware defaults. Derive the default region from the request locale, user profile, or a config value rather than hardcoding. Store it alongside the number if you support multiple regions, because validation is region-sensitive.
Composable display helpers. Wrap formatting in a small PhoneFormatter service (or a Blade directive) so views don't call the phone-number utility directly. This also gives you a single place to handle parse failures for legacy/garbage data.
Testing. Use real fixture numbers from the library's example data per region; assert both validation outcomes and exact normalized output. Include an invalid-length and a wrong-country-code case.
4.0.1 behavior change — country choices are no longer translated. PhoneNumberType previously ran the country list through the translator; as of 4.0.1 the choices are untranslated. If you relied on localized country names in the dropdown, you must now supply your own translated labels (e.g. a custom choice_label callback or your own choice list). This is a silent UX regression for non-English locales — no exception, just English country names. Audit any locale-specific tests or screenshots.
Validate on E.164, not on raw strings. isValidNumber is stricter and more correct than isPossibleNumber; use the latter only for soft warnings mid-typing.
Exceptions are part of the contract. Parsing throws on malformed input (NumberParseException). Never let that escape a request handler — catch and convert to a validation failure.
Region defaults bite. A number entered without a country code is interpreted against the default region. If your default region drifts (e.g. from config), previously valid stored inputs may re-validate differently. Always persist E.164.
Metadata staleness. The underlying libphonenumber metadata ages; new number ranges appear regularly. Pin and periodically bump the underlying library, and re-run your fixture tests after upgrades.
Bundle bootstrapping in Laravel. There is no Laravel service provider shipped. You'll need to register the bundle's services manually or resolve classes individually. If you only need parsing/formatting, skip the bundle's DI entirely and use the underlying library — fewer moving parts and no Symfony Form dependency.
Extension points. The form type and validator are the intended customization seams (custom choice_label, custom message/constraint options). For anything more exotic, wrap the phone-number utility in your own service rather than subclassing bundle internals, which are not covered by a stability guarantee across majors.
Version awareness. 4.0.1 is a patch-level fix, but the change is user-visible. Treat minor/patch bumps of this bundle as potentially behavior-changing and pin exact versions in composer.json if localized country labels matter to you.
How can I help you explore Laravel packages today?