Форматирование телефонных номеров на PHP

    Возникла задача автоматического форматирования телефонных номеров в виде страна (город) номер, и первым делом я обратился к существующим решениям.
    К сожалению, оказалось, что все найденные решения основываются на обычном подгоне строки под пользовательский формат, имея ограниченную область применения и ошибки при выходе за ее пределы.

    Для начала приведу обзор найденных решений. Тем, кому это не интересно, рекомендую прокрутить ниже до заголовка «Форматы телефонных номеров» — там уже представлен мой вариант разбора номера с ссылкой на код.

    Всеуничтожающий примитив

    (Найденное решение. Мое ниже)
    Первое, на что я наткнулся — были сообщения на форумах и банки скриптов, предлагающие решения следующего плана:
    <?
    function phone_number($sPhone){
        $sPhone = ereg_replace("[^0-9]",'',$sPhone);
        if(strlen($sPhone) != 10) return(False);
        $sArea = substr($sPhone, 0,3);
        $sPrefix = substr($sPhone,3,3);
        $sNumber = substr($sPhone,6,4);
        $sPhone = "(".$sArea.")".$sPrefix."-".$sNumber;
        return($sPhone);
    }
    ?>

    Один из простых вариантов шустрого форматирования телефонных номеров, но каждое такое решение ориентировано на телефонные номера из конкретной локальной зоны и не является решением задачи.

    Форматирование с помощью sscanf

    (Найденное решение. Мое ниже)
    function formatPhone($phone) {
        if (empty($phone)) return "";
        if (strlen($phone) == 7)
            sscanf($phone, "%3s%4s", $prefix, $exchange);
        else if (strlen($phone) == 10)
                sscanf($phone, "%3s%3s%4s", $area, $prefix, $exchange);
            else if (strlen($phone) > 10)
                    if(substr($phone, 0,1)=='1') {
                        sscanf($phone, "%1s%3s%3s%4s", $country, $area, $prefix, $exchange);
                    }
                    else{
                        sscanf($phone, "%3s%3s%4s%s", $area, $prefix, $exchange, $extension);
                }
                else
                    return «unknown phone format: $phone»;
        $out = "";
        $out .= isset($country)? $country.' ': '';
        $out .= isset($area)? '('. $area. ') ': '';
        $out .= $prefix. '-'. $exchange;
        $out .= isset($extension)? ' x'. $extension: '';
        return $out;
    }

    Не смотря на простое решение, эта функция уже умеет форматировать номера длиной 7, 10 и более цифр, но попадись ей номер из российской глубинки, она подавится и выдаст ошибочный результат.

    Symfony, lib/helpers/PhoneHelper.php, format_phone

    (Найденное решение. Мое ниже)
    <?php
    function format_phone($phone = '', $convert = false, $trim = true)
    {
        // If we have not entered a phone number just return empty
        if (empty($phone)) {
            return '';
        }
     
        // Strip out any extra characters that we do not need only keep letters and numbers
        $phone = preg_replace("/[^0-9A-Za-z]/", "", $phone);
     
        // Do we want to convert phone numbers with letters to their number equivalent?
        // Samples are: 1-800-TERMINIX, 1-800-FLOWERS, 1-800-Petmeds
        if ($convert == true) {
            $replace = array('2'=>array('a','b','c'),
                     '3'=>array('d','e','f'),
                         '4'=>array('g','h','i'),
                     '5'=>array('j','k','l'),
                                     '6'=>array('m','n','o'),
                     '7'=>array('p','q','r','s'),
                     '8'=>array('t','u','v'), '9'=>array('w','x','y','z'));
     
            // Replace each letter with a number
            // Notice this is case insensitive with the str_ireplace instead of str_replace 
            foreach($replace as $digit=>$letters) {
                $phone = str_ireplace($letters, $digit, $phone);
            }
        }
     
        // If we have a number longer than 11 digits cut the string down to only 11
        // This is also only ran if we want to limit only to 11 characters
        if ($trim == true && strlen($phone)>11) {
            $phone = substr($phone,  0, 11);
        }
     
        // Perform phone number formatting here
        if (strlen($phone) == 7) {
            return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1-$2", $phone);
        } elseif (strlen($phone) == 10) {
            return preg_replace("/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "($1) $2-$3", $phone);
        } elseif (strlen($phone) == 11) {
            return preg_replace("/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/", "$1($2) $3-$4", $phone);
        }
     
        // Return original phone if not 7, 10 or 11 digits long
        return $phone;
    }
    ?>

    Функция позволяет не только форматировать в XXX-XXXX, (XXX) XXX-XXXX и X (XXX) XXX-XXXX, но и конвертировать номера, написанные цифрами. Ограниченность функции в форматировании номеров длиной 7, 10 и 11 символов никак не подходит.

    Форматы телефонных номеров

    Из вики-статьи видно, что никакого простого и удобного паттерна для быстрого форматирования всех номеров не существует. Коды стран регистрируются, подобно доменным зонам, а коды городов — остаются на совести каждой из стран.

    Другими словами, маршрутизация звонков идет по маске, начиная с кода страны: звонок, направленный в конкретную страну далее пробивает себе маршрут в соответствии с кодами области, города, района и т.д. начиная с самой левой цифры, пока последнее звено не перебросит его на конкретный телефонный/факсовый аппарат. Проблема усложняется еще и тем, что коды городов внутри стран точно так же не поддаются единой сквозной стандартизации, т.е. в худшем из вариантов для правильного форматирования номеров придется использовать двумерный массив с кодами стран и их городов.

    На самом деле, все оказалось не так страшно. В каждой стране можно разделить все коды городов на две части: на те, что в большинстве своем совпадают по длине, и все остальные. Этого достаточно, чтобы резко сократить область перебора кодов при сравнении. Т.е. можно создать массив из данных по каждой стране вида:
    <?
    $data = Array(
    'Код страны'=>Array(
            'name'=>'Имя страны', // для удобства. Не будет использоваться.
            'cityCodeLength'=> обычная_длина_кода_города_для_этой_страны,
            'exceptions'=>Array(коды_городов_исключения),
        )
    );
    ?>
    Затем провести предварительную обработку данных, дополнив его полями, сужающими область перебора, exceptions_max и exceptions_min — максимальной и минимальной длиной кода городов-исключений, соответственно. Также необходимо учесть страны, в которых коды городов начинаются на 0 — отразим эту «особенность» полем zeroHack. Как пример:
    <?
    $data = Array(
    '886'=>Array(
            'name'=>'Taiwan',
            'cityCodeLength'=>1,
            'zeroHack'=>false,
            'exceptions'=>Array(89,90,91,92,93,96,60,70,94,95),
            'exceptions_max'=>2,
            'exceptions_min'=>2
        ),
    );
    ?>
    После этого возьмем подходящие участки кода из решений выше и сделаем функцию форматирования:
    <?
    function phone($phone = '', $convert = true, $trim = true)
    {
        global $phoneCodes; // только для примера! При реализации избавиться от глобальной переменной.
        if (empty($phone)) {
            return '';
        }
        // очистка от лишнего мусора с сохранением информации о «плюсе» в начале номера
        $phone=trim($phone);
        $plus = ($phone[ 0] == '+');
        $phone = preg_replace("/[^0-9A-Za-z]/", "", $phone);
        $OriginalPhone = $phone;
     
        // конвертируем буквенный номер в цифровой
        if ($convert == true && !is_numeric($phone)) {
            $replace = array('2'=>array('a','b','c'),
            '3'=>array('d','e','f'),
            '4'=>array('g','h','i'),
            '5'=>array('j','k','l'),
            '6'=>array('m','n','o'),
            '7'=>array('p','q','r','s'),
            '8'=>array('t','u','v'),
            '9'=>array('w','x','y','z'));
     
            foreach($replace as $digit=>$letters) {
                $phone = str_ireplace($letters, $digit, $phone);
            }
        }
     
        // заменяем 00 в начале номера на +
        if (substr($phone,  0, 2)==«00»)
        {
            $phone = substr($phone, 2, strlen($phone)-2);
            $plus=true;
        }
     
        // если телефон длиннее 7 символов, начинаем поиск страны
        if (strlen($phone)>7)
        foreach ($phoneCodes as $countryCode=>$data)
        {
            $codeLen = strlen($countryCode);
            if (substr($phone,  0, $codeLen)==$countryCode)
            {
                // как только страна обнаружена, урезаем телефон до уровня кода города
                $phone = substr($phone, $codeLen, strlen($phone)-$codeLen);
                $zero=false;
                // проверяем на наличие нулей в коде города
                if ($data['zeroHack'] && $phone[ 0]=='0')
                {
                    $zero=true;
                    $phone = substr($phone, 1, strlen($phone)-1);
                }
     
                $cityCode=NULL;
                // сначала сравниваем с городами-исключениями
                if ($data['exceptions_max']!= 0)
                for ($cityCodeLen=$data['exceptions_max']; $cityCodeLen>=$data['exceptions_min']; $cityCodeLen--)
                if (in_array(intval(substr($phone,  0, $cityCodeLen)), $data['exceptions']))
                {
                    $cityCode = ($zero? «0»: "").substr($phone,  0, $cityCodeLen);
                    $phone = substr($phone, $cityCodeLen, strlen($phone)-$cityCodeLen);
                    break;
                }
                // в случае неудачи с исключениями вырезаем код города в соответствии с длиной по умолчанию
                if (is_null($cityCode))
                {
                    $cityCode = substr($phone,  0, $data['cityCodeLength']);
                    $phone = substr($phone, $data['cityCodeLength'], strlen($phone)-$data['cityCodeLength']);
                }
                // возвращаем результат
                return ($plus? "+": "").$countryCode.'('.$cityCode.')'.phoneBlocks($phone);
            }
        }
        // возвращаем результат без кода страны и города
        return ($plus? "+": "").phoneBlocks($phone);
    }
     
    // функция превращает любое число в строку формата XX-XX-... или XXX-XX-XX-... в зависимости от четности кол-ва цифр
    function phoneBlocks($number){
        $add='';
        if (strlen($number)%2)
        {
            $add = $number[ 0];
            $add .= (strlen($number)<=5? "-": "");
            $number = substr($number, 1, strlen($number)-1);
        }
        return $add.implode("-", str_split($number, 2));
    }
     
    // тесты
    echo phone("+38 (044) 226-22-04")."<br />";
    echo phone(«0038 (044) 226-22-04»)."<br />";
    echo phone("+79263874814")."<br />";
    echo phone(«4816145»)."<br />";
    echo phone("+44 (0) 870 770 5370")."<br />";
    echo phone(«0044 (0) 870 770 5370»)."<br />";
    echo phone("+436764505509")."<br />";
    echo phone("(+38-048) 784-15-46 ")."<br />";
    echo phone("(38-057) 706-34-03 ")."<br />";
    echo phone("+38 (044) 244 12 01 ")."<br />";
    ?>

    , где global $phoneCodes; — тот самый массив с информацией по всем странам.

    Выведет
    +380(44)226-22-04<br/>+380(44)226-22-04<br/>+7(926)387-48-14<br/>481-61-45<br/>+44(0870)770-53-70<br/>+44(0870)770-53-70<br/>+43(6764)50-55-09<br/>380(4878)415-46<br/>380(5770)634-03<br/>+380(44)244-12-01

    Функция полностью решает поставленную задачу.
    Из недостатков функции следует отметить отсутствие анализа медленных участков с целью оптимизаци, а также обработки телефонных номеров, где есть код города, но нет кода страны (в этом случае достаточно бить на блоки функцией phoneBlocks или воспользоваться одним из решений выше). При использовании ее в какой-либо реализации необходимо заменить глобальную переменную на ссылку в параметре, а также можно доработать или заменить формат вывода, за который отвечает функция phoneBlocks.

    Самое интересное

    Используя информацию с сайтов:
    http://www.mtt.ru/info/def/index.wbp
    http://www.hella.ru/code/codeuro.htm
    http://www.scross.ru/guide/phone-global/
    я собрал массив данных по всем представленным странам, включая города-исключения, флаги zeroHack, а также коды мобильных сетей. Код можно загрузить здесь.

    Быстродействие

    Вопреки всем самым пессимистичным ожиданиям, код отрабатывает 10.000 номеров менее чем за 2 секунды.

    UPD Готовятся поправки:
    1. поддержка паттернов форматирования, принятых внутри конкретных стран («локально-принятые» нормы отображения номеров);
    2. добавление флага для указания, относительно какой страны выполнять форматирование номера;
    3. добавление параметра для указания формата вывода (в случае личных предпочтений и исключений);
    4. поддержка нелатинских буквенных номеров
    5. определение сотовых номеров и замена скобок на пробелы
    UPD: Архив пропал с сервера, выложил на https://github.com/mrXCray/PhoneCodesСкоро будет обновление по поправкам выше + бонус.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 97
    • –1
      У меня таким макаром:
      // function phone($number, $format = '[1] [(3)] 3-2-2'){
      
      echo phone('+7 812 123 45 67'); // +7 (812) 123-45-67
      echo phone('8 812 123 45 67'); // 8 (812) 123-45-67
      echo phone('8121234567'); // (812) 123-45-67
      echo phone('1234567'); // 123-45-67
      
      $format = '[([1 ]4)] 2-2-2';
      
      echo phone('+78121234567', $format); // (+7 8121) 23-45-67
      echo phone('88121234567', $format); // (8 8121) 23-45-67
      echo phone('8121 23 45 67', $format); // (8121) 23-45-67
      echo phone('234567', $format); // 23-45-67


      Буквы на цифры не подменяю, кроме как для США и нескольких стран такого и не нужно.
      • 0
        Замечательная функция, буду использовать.
        А как быть с номерами, в которых указывается добавочный номер?
        Например, +7 123 456-7899 доб. 777
        • НЛО прилетело и опубликовало эту надпись здесь
          • 0
            Например, так: +7 123 456-7899P777
        • НЛО прилетело и опубликовало эту надпись здесь
          • +1
            У Нижневартовска код города 3466, у Мегиона (городок Нижневартовском районе) — 34663
            Вот так вот все непросто :)
            • НЛО прилетело и опубликовало эту надпись здесь
              • +1
                еще нашел: Уфа 347, Стерлитамак 3473, Агидель 34731 :-)
                • НЛО прилетело и опубликовало эту надпись здесь
                  • НЛО прилетело и опубликовало эту надпись здесь
                    • 0
                      Т.е. «гулять» по дереву нужно будет с запасом, что вполне реализуемо. Собственно, этот вариант лучше только с той т.з., что помимо сформатированного номера мы получаем еще и название города.
                    • 0
                      Уфа и Казань перешли с 6-значных номеров на семизначные, поэтому так.
                      Например в Казани был код 8432, а теперь добавился еще код 8435 — номера стали 2XX-XX-XX и 5XX-XX-XX
                • 0
                  Да я б с удовольствием, но при этом, если я правильно понял, нужно вбивать в массив данных все города мира, а у меня достаточно проверить исключения, а потом просто «вырезать» фиксированное количество знаков, если среди исключений совпадений нет.
                  • 0
                    Имеет смысл сделать дерево с несколькими вершинами, чтобы использовать его не только по прямому назначению, но и для форматирования номеров, где код города присутствует, а страна — нет.
                    • 0
                      А ещё есть (были точно) приколы, что у города два кода одновременно, например 4152 и 41522 — использовалось (а может и используется до сих пор) при переходе с 5-ти значной городской нумерации на 6-ти значную, то есть часть городских номеров 5-ти значная, а часть 6-ти
                      • 0
                        Ну вы же понимаете что разница только в записи? Тоесть во втором случае код тоже 4152, просто все номера начинаются с двойки.
                        • 0
                          Судя по данным с www.hella.ru/code/codeuro.htm, это именно код пятизначный, т.е. в одном случае (4152), а в другом — (41522). А вот с т.з. маршрутизации на АТС-ках, Вы правы, им достаточно тупо проверить пятую цифру кода и направить ее либо в область, либо в город.
                          • 0
                            Для междугородней записи разницы нет, а вот для жителей города, афаик, есть разница, как-то там одновременно сосуществуют (если не закончился этот бардак) 5-ти и 6-ти значные номера (кажется цифровые АТС перевели на 6-ти значные, а «декадно-шаговые» оставили на 5-ти значных). Увидев запись где-нить в профиле на форуме 8(4152)25-55-55 :) многие будут набирать 25-55-55, хотя надо 5-55-55 (может цифровая АТС и соединит по 25-55-55 с 5-55-55, но это будет фича, а аналоговая, афаик, не соединит, поскольку не сообразит, что первую цифру ей надо игнорить)
                          • 0
                            В Харькове тоже самое сейчас — 057 и 0572
                        • –1
                          Внутри города Стаханова телефоны из 5-ти знаков 4-11-11. Зачастую его так и дают в городских СМИ.
                          • 0
                            Внес поправку в функцию phoneBlocks. Номера короче 6 символов форматируются как X-XX-XX (без выделения первых трех).
                          • 0
                            А как кто хранит телефонный номера в базе? В том формате как ввел пользователь или приводите к какому-то стандарту?

                            Если в формате «как ввел пользователь» встает проблема поиска одинаковых номеров по базе. Один пользователь ввел +7915 другой 8915 итп
                            • 0
                              Проблема не встает — просто приводи их в единый формат при записи в БД, а окошко с телефонным номером дели на три части для того, чтобы было очевидно, что нужно ввести не только локальный номер, но и указать код города и страны.
                            • +1
                              Очень круто код подсветили, пользовались чем-то или, не дай бог, вручную?

                              А результат, вам неплохо было бы оформить в виде pear-модуля например
                            • 0
                              Только у нас принято в 7-значных номерах разделять последние четыре цифры по две, т.е. 654-12-43.
                              А то что вы написали, это английский стандарт — у них вообще принято произносить большие числа группами (например год), у нас такого нет.
                              Вообще один известный дизайнер все правильно по этому поводу написал.
                              • 0
                                В примере вывода написано же:

                                481-61-45 — Вы ведь это имеете в виду?
                                • 0
                                  Это, но касательно первой части статьи: там, где XXX-XXXX, (XXX) XXX-XXXX и X (XXX) XXX-XXXX.
                                  • +1
                                    Так ведь там разбираются методы, которые как раз не являются решением )
                              • +4
                                Итак, я поставил вам минус. Давайте разберемся, за что именно. Для начала, вы не учитываете того, что в разных странах используются разные разделители групп:

                                Сравните 640.453.4513 и 07787 525 123.

                                Дальше. Сравните номер в Лондоне и Эдинбурге:

                                (020) 7567 8452 и (0131) 123 2153.

                                Дефисов пока не видно. Ни одного. А вот вам телефонный номер в Париже:

                                01 22 14 34 98 — странно выглядит, правда? А если записать его в международном формате, то еще страннее: +33 1 2214 3498.

                                Короче говоря, пилить и пилить. Делать локальные решения в стиле «А у нас нет клиентов за МКАДом» уже давно не модно, решение этой задачи существуют и я предлагаю вам воспользоваться поисковиком для этого и найти хорошо работающее, проверенное решение данной задачи. Или же решайте задачу целиком, вместе со всеми исключениями и edge case'ами.
                                • 0
                                  Справедливости ради, библиотека на которую я указал, тоже отнюдь не совершенна.
                                  • 0
                                    и не на PHP :) Но доработать надо в любом случае.
                                    • –1
                                      в данном случае я отношу это к ее преимуществам ;)
                                  • 0
                                    Окей, согласен.
                                    Добавлю паттерн форматирования к информации о стране в базу и обновлю код.

                                    Спасибо.
                                    • +1
                                      Тут вообще-то все еще хуже. Во-первых, если ты не знаешь страну, для которой делаешь форматирование, задача уже нерешаемая в принципе. Во-вторых, в зависимости от конкретного города (Самара vs Нижневартовск) форматирование номера будет отличаеться т.к. разной длины нумерация, но ты это учитываешь. Теперь надо убиться и выяснить информацию по нумерации для всех городов мира. Это сложно, но возможно — но здесь уже придется банально искать баланс между универсальностью и сложностью.
                                      • 0
                                        1. добавить информацию для каждой страны касательно формата разбиения групп цифр
                                        2. добавить флаг в функцию, который бы позволял указывать страну в виде международного кода, относительно которой необходимо форматировать
                                        3. добавить возможность самостоятельно указать паттерн форматирования
                                  • 0
                                    Это задача из Data Quality.
                                    Простым деревом не решить.
                                    тут нужен аналог КЛАДР.
                                    Ведь как ни крути а телефонный номер — это тот же адрес, только электронный. В нем зашита и страна, и город, и даже район города, а при нужных базах дом и квартира. =)

                                    А так как адреса никогда в дерево утрамбовать не получиться, то и решение идеальным не будет.

                                    То что у вас сделано, достойно, но не покрывает всего поля вариантов.

                                    Есть специальные сервисы которые зарабатывают на такой чистке данных.
                                    Поверьте на слово, они не будут продавать услуги основанные на простом коде, так как задача требует сложных алгоритмов.

                                    В российском сегменте такими задачами занимаются iqsystems.ru и hflabs.ru

                                    Есть еще, но эти компании наиболее продвинулись по данному направлению.
                                    • 0
                                      substr($phone, 0,1)=='1'
                                      Что это? $phone[0] == '1'
                                      • 0
                                        Пожалуйста, читайте внимательнее. Мое решение ниже. То, к чему Вы придрались — находится среди разбора существующих и неподходящих решений.
                                    • 0
                                      $phone = preg_replace("/[^0-9A-Za-z]/", "", $phone);
                                      Ваша функция не понимает UTF-8? Какую кодировку она вообще-то требует?
                                      • +1
                                        UTF? В телефонном номере? Вы в ту ветку пишете? :)
                                        Или я отстал от жизни и телефонные номера теперь указывают в UTF?
                                        • +1
                                          Процитированный мною код удаляет из строки все не цифры и не латинские буквы. Из чего я делаю вывод, что вы там ожидаете любые символы. В какой кодировке вы ожидаете эти символы?

                                          Кроме того, взгляните на клавиатуру любого русифицированного офисного телефона. Там есть русские буквы. Не только американцы подбирают буквенные аналоги к номерам своих телефонов, в России это не прижилось, но в европах встречается, а там (сюрприз!) не всегда только латинские буквы. А их надо как-то кодировать.
                                          • 0
                                            Да, этот узел доработаю. Честно говоря, не встречал номеров с умлаутами и прочим, но обязательно покопаюсь в этом вопросе.
                                      • +5
                                        > +7(926)387-48-14

                                        Брать цифры сотового номера в скобки неграмотно. В скобки берут те цифры, которые при определённых условиях можно не набирать. В случае с сотовым номером таких условий не существует. Открывающую скобку от предыдущей цифры и закрывающую от последующей следует отбивать пробелом.
                                        • +1
                                          Добавляю в список планируемых фиксов, спасибо.
                                          • +1
                                            Пардоньте, промахнулся и поставил минус, исправился кармически
                                            Добавлю, что по этому же обоснованию в случае городских номеров в скобки заключается и код старны и код города, разделяемые пробелом.
                                        • 0
                                          Вот кстати молодая рубёвая библиотека для тех же целей: github.com/elskwid/phone
                                          Вот отсюда rubygems.org/gems/elskwid-phone/ можно забрать gem и потестить, как работает либа через irb.
                                          • +1
                                            +44(0870)770-53-70
                                            вот этот номер неправильный. Должно быть +44 (0) 870-770-53-70.
                                            0 в скобках, потому что внутри страны набирается начиная с 0. А +44 — то, что его заменяет при звонках извне.
                                            870 — код города (ну или области)
                                            • 0
                                              Обсуждалось выше уже, но не могли бы Вы уточнить, код города всегда указывается вне скобок в этом случае?
                                              Т.е. если я живу в городе 870, я все равно должен набирать 870-…, а если в другом, то 0-870-…?
                                              • 0
                                                То есть российские номера в это стандарте должны выглядеть так: +7 (8) 123-456-78-90?
                                                • 0
                                                  Нет, не так. +7 и (8) вместе — это уже ошибкой пахнет :)
                                                  Из обсуждений выше видно, что в скобках указывается то, что набирать необязательно.

                                                  Другими словами, для москвичей (если бы остался 095, а не два кода, будь они неладны), указывалось бы 123-45-67 при локальном номере, 8 812 123-45-67, при междугороднем, а т.к. мы часто не знаем, откуда читатель номера, то пишем 8 (812) 123-45-67, намекая на то, что 812 набирать надо только в случае, если номер нелокален.

                                                  Напротив дело обстоит с мобильными операторами. Получается, что писать 8(926)123-45-67 в корне неправильно, т.к. без 926 номер набирать нельзя.

                                                  Я обязательно покопаюсь подробно в этой теме и выкачу новый топик вместе с реализованными находками в обновленном коде функции, преобразовав ее в полноценный класс.
                                                  • +1
                                                    >8 812 123-45-67, при междугороднем, а т.к. мы часто не знаем, откуда читатель номера, то пишем 8 (812) 123-45-67, намекая на то, что 812 набирать надо только в случае, если номер нелокален.

                                                    Но если номер локален, то и 8 не надо набирать, а извне страны надо набирать +7.

                                                    В общем по хорошему надо отслеживать посетителя, для местных писать 123-45-67, для той же страны (8 812) 123-45-67 или (812) 123-45-67 — про 8 сам догадается :), для других стран (+7 812) 123-45-67.

                                                    Хотя, если это на сайте рассчитанном на российскую аудиторию, то, наверное, вполне достаточно (812) 123-45-67. Иностранец, по идее, должен сообразить и узнать код России (тем более что ему возможно не +7 надо будет вводить, а какой-нибудь 110 7). Если же не заморачиваться, то в хидере/футере сайта писать (812) 123-45-67, а в «Контактах» несколько вариантов одного и того же номера типа
                                                    USA: 110 7 812 123-4567
                                                    Europe: 00 7 812 123.45.67
                                                    Mobile: +7 812 123-45-67

                                                    или везде ограничиться +7 812 123-45-67

                                                    • 0
                                                      > Иностранец, по идее, должен сообразить и узнать код России
                                                      Люди такие люди, что лучше указать полный номер телефона для иностранца.

                                                      В целом согласен, потому и сделаю параметр для того, чтобы можно задавать паттерн вывода, если мнение функции расходится с мнением ее пользователя.
                                                  • 0
                                                    На самом деле как раз и будет выглядеть так, как вы написали :) Это же смесь международного и локального набора. В ЮК этот номер набирался бы 0870 770 53 70.
                                                    А набирают там по-моему всегда полный номер. Но могу ошибаться.
                                                • –2
                                                  Тесты в виде echo — это шикарно. Я уж не говорю про PHPUnit, но может быть всё-таки сделать отдельный файл с проверками?
                                                  • +1
                                                    Уважаемый, Вы совсем потеряли восприятие размеров?)) PHPUnit на одной функции? Отдельный файл с проверками? :)
                                                    Это демонстрация, а не тесты, если уж совсем быть точным.
                                                    Я понимаю, что существуют «паттерны правильного программирования», но не надо бить по мухе и пушки ;-)
                                                    • 0
                                                      «по мухе ИЗ пушки», конечно, «из»…
                                                  • 0
                                                    решали и решили телефоны полгода назад — помогает нам база кодов всех стран и всех городов.
                                                    Ито, иногда оказывается что год назад какойнить город перешол с пятизначки на четырех значку или наоборот и надо править.

                                                    Кстати — для тех кто в танке — БОЛЬШАЯ часть России использует четырехзначный и более код города.
                                                    • 0
                                                      бОльшая часть РФ использует пятизначный код города.
                                                      • 0
                                                        А где решили, можно посмотреть?
                                                        • 0
                                                          www.gdeetotdom.ru/personal/realty/new.php
                                                          забиваете номер телефона( правый верхний угол)
                                                          переводите фокус на другой елемент и жмете ф5( и соглашаетесь уйти со страницы )
                                                          после обновления получитите «правилный номер»

                                                          правда, если номер системе не понравиться — не получите вообще ничего :)
                                                      • 0
                                                        а как поддерживать код?
                                                        перенумерация (дефалтсити тому пример), присоединение населенных пунктов.
                                                        да и просто новые коды городов?
                                                        • 0
                                                          Если Вы читали внимательно, то должны были заметить, что в отличие от kashey, который использует полную тяжелую базу данных кодов всех стран и всех городов, я использую лишь информацию о том, сколько цифр в коде города для каждой страны является обычным + список исключений.

                                                          Таким образом, при появлении нового областного города в РФ, например, моя БД будет продолжать оставаться актуальной, т.к. областной город скорее всего получит пятизначный код, а вот kashey (если он достаточно описал свой метод) придется добавить этот город в свою базу.
                                                          • 0
                                                            не придется.
                                                            есть режим «работы по дефолту»
                                                            в основном используется для разных там мобилок.
                                                            В общем случае исходя из обшего колличества цифер можно предположить сколько из них вероятно будет в коде города
                                                            • 0
                                                              При коде города в 3 цифры, номер составляет 7.
                                                              • 0
                                                                Прошу прощения, нажал CTRL+Enter и не дописал…
                                                                При коде города в 4 цифры, номер составляет 6.
                                                                При коде в 5 цифр, на номер будет 5.

                                                                Во всех случаях полный номер с кодом города остается одинаковой длины, как Вы определяете вероятность?)
                                                                • 0
                                                                  Эмпирически :)
                                                                  особенно в этом нам помогают конечные люди которые звонял и жалуются что «в новосибирске построили новую АТС и вот у нас код города на одну циферку меньше стал»
                                                                  Правда потом звонят и говорят что вообще старый номер тоже правильный, и только полгорода перешло на новую нумерацию…

                                                                  Это я к тому что на самом деле — не благодарное это дело, и единственный нормальный вариант — работать через БД, которую и пополнять по возможности.
                                                                  • 0
                                                                    Понятно, но по сути получается что модель работы в любом случае практически одна и та же: у Вас, насколько я понял, полная БД городов и стран, а у меня более оптимизированная по весу с использованием исключений для тех городов, которые выбиваются из, например, правила «в РФ города имеют 5-значный номер».

                                                                    Код Вашей базы, естественно, Вы никуда не выкладывали?
                                                                    • +1
                                                                      да я его сам молча спарсил с онлайн справочников
                                                              • 0
                                                                Посмотрел форму, красиво отрабатывает, правда почему-то спрашивает про сохранение только один раз, при втором обновлении страницы уже ничего не сохраняет.
                                                                • 0
                                                                  ну во первых — иногда глючит, чего уж скромничать
                                                                  а во вторых — включается только если вы меняли данные и скрипты успели это заметить( а onchange обычно возникает при onblur )
                                                                  • 0
                                                                    Ну это мелочи оффтопные) Я не придираюсь.
                                                              • +1
                                                                Все же я думаю что без базы знаний (как её реализовывать не принципиально) не обойтись.

                                                                Привязка телефонного номера должна быть к определенной территории (точность мы сами выбираем, хотим до населенного пункта — один размер базы, хотим до дома — другой).

                                                                Аналогично и с операторами сотовой связи — у них так же есть свои внутренние коды привязанные к территории страны.

                                                                • 0
                                                                  Все верно.

                                                                  В моем решение как раз используется некоторая база данных, позволяющая однозначно определить размер кода города, основываясь на доступных в справочниках данных, в том числе имеется список кодов сотовых операторов.
                                                                  • +1
                                                                    А как дела с полнотой, точностью и актуальностью у этой БД?
                                                                    Основная проблема в DQ именно актуальные справочники.
                                                                    • 0
                                                                      Источники указаны в конце статьи, для россии был использован www.hella.ru/code/codcity.htm и МТТ.
                                                                      Собственно, т.к. данная тема получила поддержку и развитие, к поправкам функции добавлю еще пару источников и укажу наиболее актуальные для дальнейшего отслеживания.
                                                                      • +1
                                                                        Источник неофициальный => актуальность, точность, полнота может страдать.

                                                                        А вообще рад что эта тема начинает развиваться.

                                                                        потому как давно известно «Мусор на входе = мусор на выходе»
                                                            • +2
                                                              попросили опубликовать:

                                                              Здравствуйте, Александр!

                                                              На хабре не зарегистрирован, поэтому не могу прокомментировать статью Форматирование телефонных номеров на PHP, а контакты автора не нашел. Увидел, что Вы принимаете активное участие в обсуждении, поэтому написал Вам с просьбой опубликовать мой комментарий. Прошу прощения за беспокойство.

                                                              В России длина телефонного номера вместе с кодом города или с кодом оператора равна точно 10 цифрам. Как заметили в комментариях коды городов «Уфа 347, Стерлитамак 3473, Агидель 34731» имеют разную длину, но в Уфе длина городского номера 7 цифр, в Стерлитамаке — 6, а в Агидели — 5, т. е. в сумме длина всегда 10 цифр.

                                                              Первые три цифры кода каждого из городов одинаковы — 347, и эти цифры можно назвать телефонным кодом региона в целом (по аналогии с КЛАДР, как это также заметили в комментариях). Для каждого региона России существует обычно один такой общий псевдокод региона, но в Москве их два — 499 и 495. Т. е. для всех регионов России существует менее 90 кодов (не настолько большая цифра, чтобы прибегать к услугам базы данных).

                                                              Распределение кодов регионов по карте России тоже имеет некоторые закономерности: 45х-49х — Центральная Россия, окруженная 81х — 87х (с севера на юг вторая цифра обычно увеличивается), в глубь страны идут 34х-39х, и Саха с Дальним Востоком имеют коды 41х-42х. Калининградская область («на отшибе») имеет код 401. Так что и на основании этих данных можно осуществить проверку правильности ввода телефонного номера.

                                                              Но не бывает правил «10 цифр (код+номер)» без исключений. Точно знаю, что в Московской области телефоны вида: (49645) х-хх-хх заменяются на (245) х-хх-хх (496 заменяется на 2), по другим регионам информации у меня нет.

                                                              Итак, регион находим по первым трем цифрам 10-значного номера, уточняем положение внутри региона добавляя еще 1 или 2 следующих цифры. Для корректного форматирования номера стационарного телефона уже придется иметь базу кодов городов, чтобы точно отделить номер от кода. Всего кодов российских городов в открытых источниках мне удалось найти более 2000.

                                                              Что касается кодов мобильных операторов, то по этому коду можно определять принадлежность номера к определенному региону. И если емкость 926 полностью принадлежит Москве и области, то в случае с 901 не все так просто, но не безнадежно. Например, (901)6000000 — (901)6009999 — это Респ. Хакасия (но не вся), а (901)9440000 — (901)9449999 — Архангельская область (тоже не вся). Для каждого региона может быть назначено несколько последовательностей (емкостей) внутри одного кода оператора. Впрочем, это уже не вопрос форматирования, т. к. длина номера с кодом и здесь — ровно 10 цифр.Общее число непрерывных последовательностей (регион, код, диапазон, дата открытия диапазона) в России более 3500, т. е. без базы данных проверить корректность номера и/или принадлежность к определенному региону также проблематично.

                                                              С уважением,
                                                              Илья
                                                              • 0
                                                                Большое спасибо за подробные разъяснения, в большинстве своем (за исключением привязки к регионам РФ) это все учтено в базе, которая прилагается к описанной функции.

                                                                Необходимые уточнения касательно внешнего форматирования, на который, как было обсуждено в комментариях, влияет относительное расположение звонящего, а также локально принятые нормы вывода телефона, будут внедрены в следующей версии кода, которой будет выделена отдельная статья с описанием изменений.
                                                                • 0
                                                                  Добавлю, что имею на руках справочник деления кодов по регионам, который будет использован для предоставления дополнительной информации по форматируемому номеру.
                                                                  • +1
                                                                    Илья прислал еще одно письмо:

                                                                    цитата:

                                                                    Два текстовых файла внутри архива. Сделал минут 20 назад.
                                                                    dl.dropbox.com/u/8251541/tel_codes.rar
                                                                    Эту ссылку можете дать на хабре, может, кому тоже пригодится. Заодно ошибки Ростелекомовские поищем вместе.
                                                                    • 0
                                                                      Невероятно полезная инфа :)
                                                                      Использовать ее можно, как я понимаю?
                                                                      • +1
                                                                        Я думаю да.
                                                                        Если будут найдены ошибки — пишите.

                                                                        ИМХО РосТелеКом — первоисточник кодов городов.
                                                                        • 0
                                                                          А такого же хорошего источника для остальных стран под рукой нет случайно?)
                                                                          • 0
                                                                            У каждой страны свой. Тут структура похожа на DNS. Так что может где то и есть корневые, но я их не знаю.
                                                                • 0
                                                                  Вопрос о symfony (правильно пишется так) — где вы нашли этот хелпер?
                                                                  В исходниках svn.symfony-project.com/branches/ его нет, и сам я его не помню тоже.
                                                                  • 0
                                                                    Нашел в гугле сразу ссылку на страницу с кодом snippets.symfony-project.org/snippet/273
                                                                    • +1
                                                                      Ну т.е. непосредственно к симфони никакого отношения не имеет.
                                                                      Но все равно поправьте, пожалуйста, в топике symphony на symfony
                                                                      • 0
                                                                        Поправил, спасибо.
                                                                  • 0
                                                                    а как дела у дальнейшей поддержки этого скрипта?
                                                                    • 0
                                                                      Спасибо за проделанную работу, очень функционально и удобно, вот только в России больше нет кода «3432» это бывший код г. Екатеринбурга, сейчас «343» уже больше 2-х лет, коды «3434» и соответсвенно «3435» при этом есть.
                                                                      Было бы отлично, если бы выложили на GitHub например, что бы можно было вносить такие правки в хеш, и соответсвенно сгружать саму актуальную версию.
                                                                      • 0
                                                                        Спасибо, очень годно, залепил класс и поставил себе за пару минут как говорится из коробки :)
                                                                        • 0
                                                                          Пост староват, но рискну выдвинуть предложение: форматировать номера можно с помощью гугловского порта libphonenumber: github.com/giggsey/libphonenumber-for-php

                                                                          Не только форматировать, но еще и нормализовывать позволяет (типа 89261234567 => +79261234567). Я пользуюсь ей сейчас для нормализации введенных клиентами телефонных номеров.
                                                                          • +1
                                                                            Идея все еще жива. В течение месяца будет отличный web-API + либы (js/php)
                                                                            За ссылку спасибо, проверим, что там внутри натворили

                                                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.