Как стать автором
Обновить

Комментарии 98

У меня таким макаром:
// 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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Я обязательно покопаюсь подробно в этой теме и выкачу новый топик вместе с реализованными находками в обновленном коде функции, преобразовав ее в полноценный класс.
>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

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

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

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

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

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

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

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

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

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

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

Все верно.

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

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

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

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

На хабре не зарегистрирован, поэтому не могу прокомментировать статью Форматирование телефонных номеров на 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, т. е. без базы данных проверить корректность номера и/или принадлежность к определенному региону также проблематично.

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

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

цитата:

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

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

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

не читал, но вот вам сообщение из будущего, с моей наивной реализацией поиска телефонов в тексте:

document.documentElement.outerText
  .match(/(\+?((\s|\(|\)|\-|)|\d+)+\d+)+/g)
  .map((raw) => ({ raw, fmt: raw.replace(/\D/g, "") }))
  .filter(({ fmt }) => fmt.length >= 9)
  .map(({ raw, fmt }) => {
    if (fmt.length === 11 && ["8", "7"].includes(fmt[0])) {
      return { raw, fmt: `+7 (${fmt.slice(1, 4)}) ${fmt.slice(4, 7)}-${fmt.slice(7, 9)}-${fmt.slice(9, 11)}` };
    }

    if (fmt.length === 10 && "9" === fmt[0]) {
      return { raw, fmt: `+7 (${fmt.slice(0, 3)}) ${fmt.slice(3, 6)}-${fmt.slice(6, 8)}-${fmt.slice(8, 10)}` };
    }
  })
  .filter(Boolean);

не продакшен реди, но теперь мы можем взять raw и заменить его на fmt в тексте

Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации