4022a71bac
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
211 lines
11 KiB
PHP
211 lines
11 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Support;
|
||
|
||
/**
|
||
* Канонический справочник субъектов РФ (1..89) — PHP-зеркало
|
||
* resources/js/constants/regions.ts (конституционный порядок, ст. 65).
|
||
* Sentinel 0 «Вся РФ» не входит (= NULL subject_code / пустой regions).
|
||
*
|
||
* ВАЖНО: при правке regions.ts синхронно править этот файл (тест RegionTagResolverTest
|
||
* «mirrors regions.ts — exactly 89» ловит расхождение по count, но не по именам —
|
||
* сверять имена вручную при изменениях).
|
||
*/
|
||
final class RussianRegions
|
||
{
|
||
/** @var array<int, string> code(1..89) => официальное имя субъекта */
|
||
public const CODE_TO_NAME = [
|
||
// 24 республики
|
||
1 => 'Республика Адыгея',
|
||
2 => 'Республика Алтай',
|
||
3 => 'Республика Башкортостан',
|
||
4 => 'Республика Бурятия',
|
||
5 => 'Республика Дагестан',
|
||
6 => 'Донецкая Народная Республика',
|
||
7 => 'Республика Ингушетия',
|
||
8 => 'Кабардино-Балкарская Республика',
|
||
9 => 'Республика Калмыкия',
|
||
10 => 'Карачаево-Черкесская Республика',
|
||
11 => 'Республика Карелия',
|
||
12 => 'Республика Коми',
|
||
13 => 'Республика Крым',
|
||
14 => 'Луганская Народная Республика',
|
||
15 => 'Республика Марий Эл',
|
||
16 => 'Республика Мордовия',
|
||
17 => 'Республика Саха (Якутия)',
|
||
18 => 'Республика Северная Осетия — Алания',
|
||
19 => 'Республика Татарстан',
|
||
20 => 'Республика Тыва',
|
||
21 => 'Удмуртская Республика',
|
||
22 => 'Республика Хакасия',
|
||
23 => 'Чеченская Республика',
|
||
24 => 'Чувашская Республика',
|
||
// 9 краёв
|
||
25 => 'Алтайский край',
|
||
26 => 'Забайкальский край',
|
||
27 => 'Камчатский край',
|
||
28 => 'Краснодарский край',
|
||
29 => 'Красноярский край',
|
||
30 => 'Пермский край',
|
||
31 => 'Приморский край',
|
||
32 => 'Ставропольский край',
|
||
33 => 'Хабаровский край',
|
||
// 48 областей
|
||
34 => 'Амурская область',
|
||
35 => 'Архангельская область',
|
||
36 => 'Астраханская область',
|
||
37 => 'Белгородская область',
|
||
38 => 'Брянская область',
|
||
39 => 'Владимирская область',
|
||
40 => 'Волгоградская область',
|
||
41 => 'Вологодская область',
|
||
42 => 'Воронежская область',
|
||
43 => 'Запорожская область',
|
||
44 => 'Ивановская область',
|
||
45 => 'Иркутская область',
|
||
46 => 'Калининградская область',
|
||
47 => 'Калужская область',
|
||
48 => 'Кемеровская область',
|
||
49 => 'Кировская область',
|
||
50 => 'Костромская область',
|
||
51 => 'Курганская область',
|
||
52 => 'Курская область',
|
||
53 => 'Ленинградская область',
|
||
54 => 'Липецкая область',
|
||
55 => 'Магаданская область',
|
||
56 => 'Московская область',
|
||
57 => 'Мурманская область',
|
||
58 => 'Нижегородская область',
|
||
59 => 'Новгородская область',
|
||
60 => 'Новосибирская область',
|
||
61 => 'Омская область',
|
||
62 => 'Оренбургская область',
|
||
63 => 'Орловская область',
|
||
64 => 'Пензенская область',
|
||
65 => 'Псковская область',
|
||
66 => 'Ростовская область',
|
||
67 => 'Рязанская область',
|
||
68 => 'Самарская область',
|
||
69 => 'Саратовская область',
|
||
70 => 'Сахалинская область',
|
||
71 => 'Свердловская область',
|
||
72 => 'Смоленская область',
|
||
73 => 'Тамбовская область',
|
||
74 => 'Тверская область',
|
||
75 => 'Томская область',
|
||
76 => 'Тульская область',
|
||
77 => 'Тюменская область',
|
||
78 => 'Ульяновская область',
|
||
79 => 'Херсонская область',
|
||
80 => 'Челябинская область',
|
||
81 => 'Ярославская область',
|
||
// 3 города федерального значения
|
||
82 => 'Москва',
|
||
83 => 'Санкт-Петербург',
|
||
84 => 'Севастополь',
|
||
// 1 автономная область
|
||
85 => 'Еврейская автономная область',
|
||
// 4 автономных округа
|
||
86 => 'Ненецкий автономный округ',
|
||
87 => 'Ханты-Мансийский автономный округ — Югра',
|
||
88 => 'Чукотский автономный округ',
|
||
89 => 'Ямало-Ненецкий автономный округ',
|
||
];
|
||
|
||
/**
|
||
* Алиасы нестандартных форм реестра Россвязи → каноничное имя субъекта.
|
||
* Города фед. значения приходят с префиксом «г. »; «Республика Удмуртская» —
|
||
* перевёрнутый порядок слов; «Кемеровская область - Кузбасс обл.» — спец-форма.
|
||
*
|
||
* @var array<string, string>
|
||
*/
|
||
private const REGION_ALIASES = [
|
||
'г. Москва' => 'Москва',
|
||
'Город Москва' => 'Москва',
|
||
'г. Санкт-Петербург' => 'Санкт-Петербург',
|
||
'г. Санкт - Петербург' => 'Санкт-Петербург',
|
||
'г. Севастополь' => 'Севастополь',
|
||
'Республика Саха /Якутия/' => 'Республика Саха (Якутия)',
|
||
'Чувашская Республика - Чувашия' => 'Чувашская Республика',
|
||
'Кемеровская область - Кузбасс обл.' => 'Кемеровская область',
|
||
'Кемеровская область - Кузбасс область' => 'Кемеровская область',
|
||
'Кемеровская область - Кузбасс' => 'Кемеровская область',
|
||
];
|
||
|
||
/** @return array<string, int> name => code (обратный индекс) */
|
||
public static function nameToCode(): array
|
||
{
|
||
return array_flip(self::CODE_TO_NAME);
|
||
}
|
||
|
||
/**
|
||
* Нормализует строку региона реестра Россвязи в каноничное имя субъекта (или null).
|
||
*
|
||
* Реестр кодирует субъект как ПОСЛЕДНИЙ сегмент после «|»
|
||
* (напр. «г. Воскресенск|р-н Воскресенский|Московская обл.» → «Московская обл.»),
|
||
* с сокращением «обл.» вместо «область» и рядом нестандартных форм (см. REGION_ALIASES).
|
||
* Безнадёжные/неоднозначные строки («-», «Российская Федерация»,
|
||
* «Москва и Московская область», «г.о. Тольятти») → null.
|
||
*/
|
||
public static function canonicalRegionName(string $raw): ?string
|
||
{
|
||
$segment = self::lastRegionSegment($raw);
|
||
if ($segment === '') {
|
||
return null;
|
||
}
|
||
|
||
// ХМАО приходит в множестве форм (em-dash/дефис, «Югра», « АО», капитализация) —
|
||
// ловим по двум устойчивым маркерам до общих правил.
|
||
if (mb_stripos($segment, 'Ханты') !== false && mb_stripos($segment, 'Мансийск') !== false) {
|
||
return 'Ханты-Мансийский автономный округ — Югра';
|
||
}
|
||
|
||
if (isset(self::REGION_ALIASES[$segment])) {
|
||
return self::REGION_ALIASES[$segment];
|
||
}
|
||
|
||
// «обл.» → «область»; « АО» → « автономный округ».
|
||
$name = (string) preg_replace('/\s*обл\.$/u', ' область', $segment);
|
||
$name = (string) preg_replace('/\s+АО$/u', ' автономный округ', $name);
|
||
// Дефис с пробелами → длинное тире (эталон: «Республика Северная Осетия — Алания»).
|
||
// Безопасно: ни одно каноническое имя не содержит дефис, окружённый пробелами
|
||
// (составные имена вроде «Кабардино-Балкарская» используют дефис без пробелов).
|
||
$name = str_replace(' - ', ' — ', $name);
|
||
|
||
if (isset(self::nameToCode()[$name])) {
|
||
return $name;
|
||
}
|
||
|
||
// Перевёрнутый порядок «Республика X» → «X Республика» (Удмуртская/Чеченская/
|
||
// Чувашская/Кабардино-Балкарская/Карачаево-Черкесская, Донецкая Народная/
|
||
// Луганская Народная). Республика-first каноны (Татарстан, Карелия…) уже
|
||
// отловлены прямым попаданием выше.
|
||
if (preg_match('/^Республика\s+(.+)$/u', $name, $m) === 1) {
|
||
$reordered = trim($m[1]).' Республика';
|
||
if (isset(self::nameToCode()[$reordered])) {
|
||
return $reordered;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/** Резолвит строку региона реестра Россвязи в subject_code (1..89) или null. */
|
||
public static function resolveSubjectCode(string $raw): ?int
|
||
{
|
||
$name = self::canonicalRegionName($raw);
|
||
|
||
return $name === null ? null : (self::nameToCode()[$name] ?? null);
|
||
}
|
||
|
||
/** Последний сегмент после «|» (субъект в формате Россвязи), trimmed. */
|
||
private static function lastRegionSegment(string $raw): string
|
||
{
|
||
$parts = explode('|', $raw);
|
||
|
||
return trim((string) end($parts));
|
||
}
|
||
}
|