F
Filament11mo ago
Noor

phone format making format wrong

Why phone mask is taking out one digit from data.... My contact was this before 555-579-4532 and I applied mask .. now it is like this +1 (555) 579-453 ->mask(RawJs::make(<<<'JS' '+1 (999) 999-9999' JS));
5 Replies
cheesegrits
cheesegrits11mo ago
Well, that's the format you've asked for. When using a mask, anything that isn't a wildcard character (*, a and 9) in your mask just gets passed through as part of the field state. Best practice for phone numbers is to always store them in a normalized format, like E164, then display them in your desired format (like National). If you look here, you'll find where I gave examples of how to do this, using the brick/phonenumber composer package. An example of a form field using this technique would be ...
Forms\Components\TextInput::make('phone')
->formatStateUsing(
fn ($state) => StringHelper::formatPhoneNumber($state)
)
->dehydrateStateUsing(fn ($state) => StringHelper::normalizePhoneNumber($state))
->mask('(999) 999 9999'),
Forms\Components\TextInput::make('phone')
->formatStateUsing(
fn ($state) => StringHelper::formatPhoneNumber($state)
)
->dehydrateStateUsing(fn ($state) => StringHelper::normalizePhoneNumber($state))
->mask('(999) 999 9999'),
Oops, forgot I'd posted that in a closed channel. I'll re-post here ... My StringHelper looks like this ... (my dfpc.phone.region is just 'US')
use Brick\PhoneNumber\PhoneNumber;
use Brick\PhoneNumber\PhoneNumberFormat;
use Brick\PhoneNumber\PhoneNumberParseException;

class StringHelper
{
public static function normalizePhoneNumber(?string $number, $strict = false, $format = PhoneNumberFormat::E164): ?string
{
$phone = null;

if (filled($number)) {
try {
$phone = PhoneNumber::parse($number, config('dfpc.phone.region'));
$valid = $strict ? $phone->isValidNumber() : $phone->isPossibleNumber();

if (! $valid) {
$phone = null;
}
} catch (PhoneNumberParseException $e) {
$phone = null;
}
}

return $phone?->format(PhoneNumberFormat::E164);
}

public static function isValidPhoneNumber(?string $number, $strict = false, $allowEmpty = true): bool
{
if (blank($number) && $allowEmpty) {
return true;
}

try {
$phone = PhoneNumber::parse($number, config('dfpc.phone.region'));

return $strict ? $phone->isValidNumber() : $phone->isPossibleNumber();
} catch (PhoneNumberParseException $e) {
return false;
}
}

public static function formatPhoneNumber(?string $number, $strict = false, $format = PhoneNumberFormat::NATIONAL): ?string
{
if (! filled($number)) {
return null;
}

try {
if ($strict) {
return self::normalizePhoneNumber($number, true, $format);
} else {
return PhoneNumber::parse($number, config('dfpc.phone.region'))->format($format);
}
} catch (PhoneNumberParseException $e) {
return null;
}
}
}
use Brick\PhoneNumber\PhoneNumber;
use Brick\PhoneNumber\PhoneNumberFormat;
use Brick\PhoneNumber\PhoneNumberParseException;

class StringHelper
{
public static function normalizePhoneNumber(?string $number, $strict = false, $format = PhoneNumberFormat::E164): ?string
{
$phone = null;

if (filled($number)) {
try {
$phone = PhoneNumber::parse($number, config('dfpc.phone.region'));
$valid = $strict ? $phone->isValidNumber() : $phone->isPossibleNumber();

if (! $valid) {
$phone = null;
}
} catch (PhoneNumberParseException $e) {
$phone = null;
}
}

return $phone?->format(PhoneNumberFormat::E164);
}

public static function isValidPhoneNumber(?string $number, $strict = false, $allowEmpty = true): bool
{
if (blank($number) && $allowEmpty) {
return true;
}

try {
$phone = PhoneNumber::parse($number, config('dfpc.phone.region'));

return $strict ? $phone->isValidNumber() : $phone->isPossibleNumber();
} catch (PhoneNumberParseException $e) {
return false;
}
}

public static function formatPhoneNumber(?string $number, $strict = false, $format = PhoneNumberFormat::NATIONAL): ?string
{
if (! filled($number)) {
return null;
}

try {
if ($strict) {
return self::normalizePhoneNumber($number, true, $format);
} else {
return PhoneNumber::parse($number, config('dfpc.phone.region'))->format($format);
}
} catch (PhoneNumberParseException $e) {
return null;
}
}
}
cheesegrits
cheesegrits11mo ago
It uses this composer package ... https://github.com/brick/phonenumber
GitHub
GitHub - brick/phonenumber: A phone number library for PHP
A phone number library for PHP. Contribute to brick/phonenumber development by creating an account on GitHub.
cheesegrits
cheesegrits11mo ago
And a column using it might look like this ... it uses formatStateUsing to format the number, and a custom query on the searchable() to ignore formatted characters when searching.
TextColumn::make('phone')
->searchable(query: function (Builder $query, string $search) {
$numbers = preg_replace('/[^0-9]/', '', $search);

if (filled($numbers)) {
return $query->where('phone', 'like', '%' . $numbers . '%');
} else {
return $query;
}
})
->formatStateUsing(
fn ($state) => StringHelper::formatPhoneNumber($state)
),
TextColumn::make('phone')
->searchable(query: function (Builder $query, string $search) {
$numbers = preg_replace('/[^0-9]/', '', $search);

if (filled($numbers)) {
return $query->where('phone', 'like', '%' . $numbers . '%');
} else {
return $query;
}
})
->formatStateUsing(
fn ($state) => StringHelper::formatPhoneNumber($state)
),
If you've already got phone numbers stored non-normalized, you'd have to whip up a little script to normalize them in place. Here's an Artisan command I wrote to do that for my tables, obviously you'd have to tweak it with your tables / models / field name(s) ...
<?php

namespace App\Console\Commands;

use App\Models\Personnel\Contact;
use App\Models\Personnel\Personnel;
use App\Models\Personnel\Vendor;
use App\Support\Helpers\StringHelper;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;

class NormalizePhoneCommand extends Command
{
protected $signature = 'dfpc:normalize-phone {--table=}';

protected $description = 'Normalize phone numbers';

public function handle(): void
{
$tables = [];
$table = $this->option('table');

if ($table) {
$tables[] = $table;
} else {
$tables = [
'contacts',
'vendors',
'personnel',
];
}

foreach ($tables as $table) {
/** @var Model $class */
$class = match ($table) {
'personnel' => Personnel::class,
'vendors' => Vendor::class,
'contacts' => Contact::class,
};

$class::chunk(100, function ($records) use ($table) {
foreach ($records as $record) {
$phone = $record->getAttribute('phone');
$normalizedPhone = StringHelper::normalizePhoneNumber($phone);

if ($normalizedPhone && $phone !== $normalizedPhone) {
$this->line($table . ': ' . $phone . ' => ' . $normalizedPhone);

$record->update([
'phone' => $normalizedPhone,
]);
}
}
});
}
}
}
<?php

namespace App\Console\Commands;

use App\Models\Personnel\Contact;
use App\Models\Personnel\Personnel;
use App\Models\Personnel\Vendor;
use App\Support\Helpers\StringHelper;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Model;

class NormalizePhoneCommand extends Command
{
protected $signature = 'dfpc:normalize-phone {--table=}';

protected $description = 'Normalize phone numbers';

public function handle(): void
{
$tables = [];
$table = $this->option('table');

if ($table) {
$tables[] = $table;
} else {
$tables = [
'contacts',
'vendors',
'personnel',
];
}

foreach ($tables as $table) {
/** @var Model $class */
$class = match ($table) {
'personnel' => Personnel::class,
'vendors' => Vendor::class,
'contacts' => Contact::class,
};

$class::chunk(100, function ($records) use ($table) {
foreach ($records as $record) {
$phone = $record->getAttribute('phone');
$normalizedPhone = StringHelper::normalizePhoneNumber($phone);

if ($normalizedPhone && $phone !== $normalizedPhone) {
$this->line($table . ': ' . $phone . ' => ' . $normalizedPhone);

$record->update([
'phone' => $normalizedPhone,
]);
}
}
});
}
}
}
End result ... phone numbers are always stored in the database normalized to E164, like +12345551212, and formatted to your desired format when displayed / masked.
Noor
Noor11mo ago
great thanks 🙂
cheesegrits
cheesegrits11mo ago
It looks like a lot of work, but once you've set it up, it really isn't. Just remember to format/dehydrate your phone fields.
Want results from more Discord servers?
Add your server
More Posts