Как правильно написать регулярное выражение для проверки корректности доменного имени?

Как определить, является ли заданная строка доменом? Рассмотрим массив строк:  
```php
$arr[] = 'site.ru';
$arr[] = 'site.com';
$arr[] = 'my-site.com';
$arr[] = 'my-cool-site.com';
$arr[] = 'my_site.com';
$arr[] = 'site123.com';
$arr[] = 'site.travel';
$arr[] = 'si$te.com';
$arr[] = 'site.r';
```

Используем следующее регулярное выражение:  
```php
$reg   = '#[a-z0-9]+[_-]?[a-z0-9]+\.[a-z]{2,}#';
foreach ($arr as $str) {
    echo $str . ' - ' . preg_match($reg, $str) . '<br>';
}
```
В результате выполнения кода мы получаем такие результаты:  
site.ru - 1  
site.com - 1  
my-site.com - 1  
my-cool-site.com - 1  
my_site.com - 1  
site123.com - 1  
site.travel - 1  
si$te.com - 1  
site.r - 0  

В чём причина того, что строка  
```php
$arr[] = 'si$te.com';
```  
определяется как доменное имя? И как это связано с тем, что если удалить тире и нижнее подчеркивание из регулярного выражения, результат всё равно останется тем же?  
```php
$reg   = '#[a-z0-9]+\.[a-z]{2,}#';
```
  • 4 сентября 2024 г. 14:43
Ответы на вопрос 3
Проблема с вашим регулярным выражением заключается в том, как оно обрабатывает символы в строке, представляющей доменное имя.

Исходное регулярное выражение:
```php
$reg = '#[a-z0-9]+[_-]?[a-z0-9]+\.[a-z]{2,}#';
```

Давайте разберемся по частям:

1. **[a-z0-9]+** — это соответствует одной или более буквам и цифрам.
2. **[_-]?** — это соответствует нулю или одному символу подчеркивания или тире.
3. **[a-z0-9]+** — снова соответствует одной или более буквам и цифрам.
4. **\.** — соответствует точке, которая разделяет имя домена и его расширение.
5. **[a-z]{2,}** — соответствует двум или более буквам, что правильно для доменной зоны.

С regex `#\.[a-z]{2,}#` доменное имя будет проверяться только на наличие точек в конце, и не будет проверяться все символы, если они соответствуют обычным правилам.

Проблема со строкой `si$te.com` заключается в следующем:

- В вашем регулярном выражении не учтено, что часть перед точкой (называемая "имя домена") может содержать только буквы и цифры, а не знаки, такие как `$`.
- Ваше регулярное выражение в текущем виде допускает теоретически любую букву, цифры и любой символ `_` или `-` именно в связи "[a-z0-9]+[_-]?[a-z0-9]+".

Теперь, давайте рассмотрим, что будет, если мы уберем тире и нижнее подчеркивание:

```php
$reg = '#[a-z0-9]+\.[a-z]{2,}#';
```
С этим выражением вы проверяете, что:
1. Имя домена начинается с хотя бы одной буквы или цифры.
2. После этого должно быть `.`, за которым следует от двух до бесконечности букв.

Данное выражение не проверяет символы до точки. Оно проверяет наличие любого подходящего домена с нужной доменной зоной.

Поэтому в результате `preg_match` вернет `1` для `si$te.com`, так как находит соответствие для `.com`, а часть перед точкой не проверяется на допустимость.

### Исправленный Регулярное выражение

Чтобы корректно проверять доменные имена, учитывая только допустимые символы, можно использовать следующее регулярное выражение:

```php
$reg = '#^(?!.*[^a-z0-9._-])[a-z0-9]+([.-][a-z0-9]+)*\.[a-z]{2,}$#';
```

- `^(?!.*[^a-z0-9._-])` — отрицательная проверка, которая гарантирует, что в строке нет недопустимых символов.
- `[a-z0-9]+` — начальная часть доменного имени.
- `([.-][a-z0-9]+)*` — допускает подчеркивание и тире как разделители между элементами доменного имени.
- `\.[a-z]{2,}$` — проверка, что за именем домена идет точка, а затем от двух до бесконечности букв.

Это регулярное выражение будет правильно проверять только существующие доменные имена.
Почему строка $arr[] = 'si$te.com'; определяется как доменное имя?

Потому что te.com определяется как доменное имя, а условие, что попадать под маску должна полная строка у вас не наложено. Ну и, на самом деле, национальные домены первого уровня в punycode могут содержать цифры.
- $reg   = '#[a-z0-9]+[_-]?[a-z0-9]+\.[a-z]{2,}#';
+ $reg   = '#^(?:[a-z0-9][a-z0-9-_]*\.)+(?:[a-z]{2,}|xn--[a-z0-9]{4,})$#i';
$arr = [
    'site.ru',
    'site.com',
    'my-site.com',
    'my-cool-site.com',
    'my_site.com',
    'site123.com',
    'site.travel',
    'si$te.com',
    'site.r',
];
foreach ($arr as $str) {
    $result = filter_var($str, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME);
    echo $str . ' - ' . ($result === false ? 'INVALID' : $result) . '<br>';
}

// site.ru - site.ru
// site.com - site.com
// my-site.com - my-site.com
// my-cool-site.com - my-cool-site.com
// my_site.com - INVALID
// site123.com - site123.com
// site.travel - site.travel
// si$te.com - INVALID
// site.r - site.r
Похожие вопросы