propaganistas/laravel-phone
Add Google libphonenumber-powered phone number validation and utilities to Laravel. Validate numbers by country (static list or matching a companion country field), cast attributes, format and compare numbers, and access phone metadata using a simple PhoneNumber class.
Installation:
composer require propaganistas/laravel-phone
Add translation for validation.php in each language file:
'phone' => 'The :attribute field must be a valid number.',
First Use Case: Validate a phone number in a form request:
use Illuminate\Validation\Rule;
$request->validate([
'phone' => ['required', 'phone:US,BE'],
]);
phone rule in Laravel's validation array or Propaganistas\LaravelPhone\Rules\Phone class.Propaganistas\LaravelPhone\PhoneNumber for formatting and parsing.RawPhoneNumberCast or E164PhoneNumberCast for Eloquent model attributes.Basic Validation:
$request->validate([
'phone' => 'phone',
]);
Automatically detects country from phone_country field.
Country-Specific Validation:
$request->validate([
'phone' => 'phone:US,FR',
]);
Dynamic Country Validation:
$request->validate([
'phone' => 'phone:country_field',
'country_field' => 'required',
]);
Type-Specific Validation:
$request->validate([
'phone' => 'phone:mobile', // Only mobile numbers
'phone' => 'phone:!fixed_line', // Exclude fixed lines
]);
Casting Attributes:
use Propaganistas\LaravelPhone\Casts\E164PhoneNumberCast;
class User extends Model
{
protected $casts = [
'phone' => E164PhoneNumberCast::class.':BE',
];
}
Handling Raw Input:
use Propaganistas\LaravelPhone\Casts\RawPhoneNumberCast;
class User extends Model
{
protected $casts = [
'phone' => RawPhoneNumberCast::class.':country_field',
];
}
Formatting:
$phone = new PhoneNumber('012 34 56 78', 'BE');
$formatted = $phone->formatNational(); // "012 34 56 78"
Country-Specific Formatting:
$phone = new PhoneNumber('012 34 56 78', 'BE');
$formattedForUS = $phone->formatForCountry('US'); // "+011 32 12 34 56 78"
Equality Checks:
$phone1 = new PhoneNumber('+3212345678');
$phone2 = new PhoneNumber('012 34 56 78', 'BE');
$phone1->equals($phone2); // true
E.164 Storage:
// Model
protected $casts = [
'phone' => E164PhoneNumberCast::class,
];
// Usage
$user->phone = '012 34 56 78'; // Automatically converts to E.164
Raw Input Storage:
// Model
protected $casts = [
'phone' => RawPhoneNumberCast::class.':BE',
];
// Usage
$user->phone = '012 34 56 78'; // Stores raw input
Search Optimization:
// Observer
public function saving(User $user)
{
if ($user->isDirty('phone')) {
$phone = phone($user->phone, $user->phone_country);
$user->phone_normalized = preg_replace('/[^0-9]/', '', $user->phone);
$user->phone_national = preg_replace('/[^0-9]/', '', $phone->formatNational());
$user->phone_e164 = $phone->formatE164();
}
}
Country Attribute Timing:
E164PhoneNumberCast, set the country attribute before the phone number to avoid exceptions:
// Wrong
$user->phone = '012 34 56 78';
$user->phone_country = 'BE';
// Correct
$user->phone_country = 'BE';
$user->phone = '012 34 56 78';
Validation Without Country:
Database Uniqueness:
E164PhoneNumberCast for unique constraints.Type Conflicts:
Validation Errors:
->after() to log validation failures:
$request->validate(['phone' => 'phone:US']);
$request->after(function ($request) {
if ($request->failed()) {
\Log::error('Phone validation failed:', $request->errors());
}
});
PhoneNumber Object Inspection:
$phone = new PhoneNumber('invalid', 'BE');
(string) $phone; // Returns E.164 or empty string
Country Code Validation:
US, BE). Invalid codes may cause silent failures.Custom Validation Rules:
Propaganistas\LaravelPhone\Rules\Phone for custom logic:
use Propaganistas\LaravelPhone\Rules\Phone as BasePhone;
class CustomPhone extends BasePhone
{
public function passes($attribute, $value)
{
if (!parent::passes($attribute, $value)) {
return false;
}
// Add custom logic
return true;
}
}
Custom Formatting:
PhoneNumber methods for project-specific formats:
class CustomPhoneNumber extends PhoneNumber
{
public function formatCustom()
{
return '(' . substr($this->getNationalNumber(), 0, 3) . ') ' .
substr($this->getNationalNumber(), 3, 3) . '-' .
substr($this->getNationalNumber(), 6);
}
}
Database Observers:
class PhoneObserver
{
public function saving(Model $model)
{
if ($model->isDirty('phone')) {
$phone = phone($model->phone, $model->phone_country ?? 'BE');
$model->phone_hash = hash('sha256', $phone->formatE164());
}
}
}
Caching PhoneNumber Objects:
PhoneNumber instances to avoid reprocessing:
$cacheKey = 'phone_' . md5($phoneString . $country);
$phone = Cache::remember($cacheKey, now()->addHours(1), function () use ($phoneString, $country) {
return new PhoneNumber($phoneString, $country);
});
Validation Optimization:
$validator = Validator::make($data, ['phone' => 'phone:US']);
if ($validator->fails()) {
// Handle errors
}
Database Indexing:
phone_normalized, phone_e164) for faster queries:
Schema::table('users', function (Blueprint $table) {
$table->index('phone_normalized');
$table->index('phone_e164');
});
How can I help you explore Laravel packages today?