feat(phone-parser): normalize RU phones and extract first candidate

This commit is contained in:
Vladimir Bryzgalov
2026-04-17 22:08:57 +05:00
parent 558483ef44
commit 3dd0f41446
2 changed files with 147 additions and 0 deletions
+72
View File
@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Cf7stg;
final class PhoneParser
{
/**
* Extract and normalize the first phone number from arbitrary text.
*
* @return array{e164:string,pretty:string,is_russian:bool}|null
*/
public static function parse(string $text): ?array
{
$text = (string)$text;
if ($text === '') {
return null;
}
// Find a run of digits, spaces, dashes, parens, dots and a leading '+',
// with at least 10 digits in total. Walk candidates left-to-right and
// pick the first that yields 10 or 11 digits.
if (!preg_match_all('/\+?[\d][\d\s().\-]{8,}/u', $text, $m)) {
return null;
}
foreach ($m[0] as $candidate) {
$digits = preg_replace('/\D+/', '', $candidate) ?? '';
$plus = strpos($candidate, '+') === 0;
// Russian detection & normalization
if (!$plus && strlen($digits) === 11 && $digits[0] === '8') {
$digits = '7' . substr($digits, 1);
}
if (!$plus && strlen($digits) === 10 && preg_match('/^[3-9]/', $digits)) {
// Bare 10-digit RU mobile/landline area code
$digits = '7' . $digits;
}
if (strlen($digits) < 10 || strlen($digits) > 15) {
continue;
}
$e164 = '+' . $digits;
$is_ru = (strlen($digits) === 11 && $digits[0] === '7');
return [
'e164' => $e164,
'pretty' => self::format_pretty($e164, $is_ru),
'is_russian' => $is_ru,
];
}
return null;
}
private static function format_pretty(string $e164, bool $is_ru): string
{
if ($is_ru) {
// +7 XXX XXX-XX-XX → "+7 (XXX) XXX-XX-XX"
$d = substr($e164, 2);
return sprintf(
'+7 (%s) %s-%s-%s',
substr($d, 0, 3),
substr($d, 3, 3),
substr($d, 6, 2),
substr($d, 8, 2)
);
}
return $e164;
}
}