Парсер телефонов и страна по умолчанию в #[Phone]
Проблема/контекст
Атрибут #[Phone] из Bitrix\Main\Validation\Rule выглядит как проверка формата строки. На деле он собирает единственный валидатор PhoneValidator, а тот вызывает Parser::getInstance()->parse($value)->isValid() — без второго аргумента. Внутри parse() пустая страна заменяется на Parser::getDefaultCountry(), поэтому один и тот же номер без + на двух порталах даст разный результат валидации.
Решение с кодом
Порядок источников в Parser::getDefaultCountry():
Option::get('main', 'phone_number_default_country')— в опции хранится ID страны, а не двухбуквенный код. Значение задаётся в настройках главного модуля полем «Страна по умолчанию».- Если опция пуста, вызывается
detectCountry(): сначалаREG_COUNTRYмодуляbitrix24, затем портальная зона из спискаbr, cn, de, in, ru, ua, by, kz, fr, pl, uz, затемContext::getCurrent()->getLanguage()из спискаbr, cn, de, in, ru, ua, by, kz, fr, pl, и только в концеGeoIp\Manager::getCountryCode(). - Найденный код конвертируется в ID и записывается в опцию — первый же вызов фиксирует страну на всё время жизни портала.
- Наружу возвращается
GetCountryCodeById($id), при неудаче — пустая строка.
Пустая строка означает, что MetadataProvider::getCountryMetadata('') вернёт false, и parse() отдаст невалидный PhoneNumber для любого номера без +. Номер с + разбирается по коду страны из самой строки, и $defaultCountry игнорируется — это единственный формат, устойчивый к настройкам портала.
В коде модуля страну задавайте явно вторым аргументом:
<?php declare(strict_types=1);
use Bitrix\Main\PhoneNumber\Format;
use Bitrix\Main\PhoneNumber\Parser;
$number = Parser::getInstance()->parse('8 (916) 123-45-67', 'RU');
$number->isValid(); // true
$number->getCountry(); // RU
$number->getCountryCode(); // 7
$number->format(Format::E164); // +79161234567
Сигнатура — PhoneNumber::format($formatType = '', $forceNationalPrefix = false). Константы класса Bitrix\Main\PhoneNumber\Format: E164 (значение 'E.164'), INTERNATIONAL, NATIONAL. При пустом $formatType вызывается Formatter::formatOriginal() — он сохраняет исходное написание и откатывается к сырой строке, если форматирование изменило состав цифр. Для невалидного номера format() всегда возвращает getRawNumber(), поэтому isValid() проверяем до форматирования, а не после.
В базе храните Format::E164 — это '+' . countryCode . nationalNumber без разделителей, единственный вид, пригодный для поиска дублей и сравнения:
<?php declare(strict_types=1);
namespace Vendor\Module\Application\Service;
use Bitrix\Main\PhoneNumber\Format;
use Bitrix\Main\PhoneNumber\Parser;
final class PhoneNormalizer
{
public function __construct(private readonly string $defaultCountry = 'RU') {}
public function normalize(string $raw): ?string
{
$number = Parser::getInstance()->parse($raw, $this->defaultCountry);
return $number->isValid() ? $number->format(Format::E164) : null;
}
}
Если валидация должна быть привязана к стране, напишите свой валидатор — интерфейс состоит из одного метода:
<?php declare(strict_types=1);
namespace Vendor\Module\Validation\Validator;
use Bitrix\Main\Localization\LocalizableMessage;
use Bitrix\Main\PhoneNumber\Parser;
use Bitrix\Main\Validation\ValidationError;
use Bitrix\Main\Validation\ValidationResult;
use Bitrix\Main\Validation\Validator\ValidatorInterface;
final class CountryPhoneValidator implements ValidatorInterface
{
public function __construct(private readonly string $country) {}
public function validate(mixed $value): ValidationResult
{
$result = new ValidationResult();
if (!is_string($value) || !Parser::getInstance()->parse($value, $this->country)->isValid())
{
$result->addError(new ValidationError(
new LocalizableMessage('MAIN_VALIDATION_PHONE_INVALID'),
failedValidator: $this,
));
}
return $result;
}
}
Подключается он через собственный атрибут, унаследованный от AbstractPropertyValidationAttribute, с возвратом валидатора из getValidators().
Итог
#[Phone] проверяет номер относительно страны портала, а не относительно вашей предметной области, и эта страна может прийти из зоны Битрикс24, языка или GeoIP. Задавайте страну явно в Parser::parse() и храните результат в Format::E164.
Комментарии (0)
Пожалуйста, войдите в аккаунт, чтобы оставить комментарий
Оставить комментарийЗагрузка...
Пока нет ни одного комментария. Будьте первым!
Похожие советы