Completed
Push — master ( 0d947c...778e4d )
by f
01:51
created

src/Russian/GeographicalNamesInflection.php (1 issue)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace morphos\Russian;
3
4
use morphos\S;
5
6
/**
7
 * Rules are from: https://ru.wikipedia.org/wiki/%D0%A1%D0%BA%D0%BB%D0%BE%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B3%D0%B5%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9_%D0%B2_%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%BC_%D1%8F%D0%B7%D1%8B%D0%BA%D0%B5
8
 */
9
class GeographicalNamesInflection extends \morphos\BaseInflection implements Cases
10
{
11
    use RussianLanguage, CasesHelper;
12
13
    protected static $abbreviations = [
14
        'сша',
15
        'оаэ',
16
        'ссср',
17
        'юар',
18
    ];
19
20
    protected static $delimiters = [
21
        ' ',
22
        '-на-',
23
        '-',
24
    ];
25
26
    protected static $ovAbnormalExceptions = [
27
        'осташков',
28
    ];
29
30
    protected static $immutableParts = [
31
        'санкт',
32
        'йошкар',
33
        'улан',
34
    ];
35
36
    protected static $runawayVowelsExceptions = [
37
        'торжо*к',
38
        'волоче*к',
39
        'орё*л',
40
    ];
41
42
    protected static $misspellings = [
43
        'орел' => 'орёл',
44
    ];
45
46
    /**
47
     * @return array|bool
48
     */
49 9 View Code Duplication
    protected static function getRunAwayVowelsList()
50
    {
51 9
        $runawayVowelsNormalized = [];
52 9
        foreach (static::$runawayVowelsExceptions as $word) {
53 9
            $runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
54
        }
55 9
        return $runawayVowelsNormalized;
56
    }
57
58
    /**
59
     * Проверяет, склоняемо ли название
60
     * @param string $name Название
61
     * @return bool
62
     */
63 2
    public static function isMutable($name)
64
    {
65 2
        $name = S::lower($name);
66
67
        // // ends with 'ы' or 'и': plural form
68
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
69
        //     return false;
70
71 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableParts, true)) {
72 2
            return false;
73
        }
74
75
        if (strpos($name, ' ') !== false) {
76
            // explode() is not applicable because Geographical unit may have few words
77
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
78
            $last_part = S::slice($name,
79
                S::findLastPosition($name, ' ') + 1);
80
81
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
82
            // N область, N край
83
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
84
                || in_array($last_part, ['край', 'область'], true)) {
85
                return true;
86
            }
87
88
            // пгт N
89
            if ($first_part === 'пгт')
90
                return false;
91
        }
92
93
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
94
        if (in_array(S::slice($name, -1), ['е', 'о'], true) && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
95
            return false;
96
        }
97
        return true;
98
    }
99
100
    /**
101
     * Получение всех форм названия
102
     * @param string $name
103
     * @return array
104
     * @throws \Exception
105
     */
106 39
    public static function getCases($name)
107
    {
108 39
        $name = S::lower($name);
109
110
        // Проверка на неизменяемость и сложное название
111 39
        if (in_array($name, static::$immutableParts, true)) {
112 1
            return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], S::name($name));
113
        }
114
115 39
        if (strpos($name, ' ') !== false) {
116 9
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
117
            // город N, село N, хутор N, пгт N
118 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
119 3
                if ($first_part !== 'пгт')
120 3
                    return static::composeCasesFromWords([
121 3
                        $first_part !== 'республика'
122 2
                            ? NounDeclension::getCases($first_part)
123 3
                            : array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part)),
124 3
                        array_fill_keys(static::getAllCases(), S::name(S::slice($name, S::length($first_part) + 1)))
125
                    ]);
126
                else
127
                    return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], 'пгт '.S::name(S::slice($name, 4)));
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 108 can also be of type boolean; however, morphos\S::slice() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
128
            }
129
130 6
            $last_part = S::slice($name,
131 6
                S::findLastPosition($name, ' ') + 1);
132
            // N область, N край
133 6
            if (in_array($last_part, ['край', 'область'], true)) {
134 2
                return static::composeCasesFromWords([static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))), NounDeclension::getCases($last_part)]);
135
            }
136
        }
137
138
        // Сложное название через пробел, '-' или '-на-'
139 36
        foreach (static::$delimiters as $delimiter) {
140 36
            if (strpos($name, $delimiter) !== false) {
141 6
                $parts = explode($delimiter, $name);
142 6
                $result = [];
143 6
                foreach ($parts as $i => $part) {
144 6
                    $result[$i] = static::getCases($part);
145
                }
146 36
                return static::composeCasesFromWords($result, $delimiter);
147
            }
148
        }
149
150
        // Исправление ошибок
151 36
        if (array_key_exists($name, static::$misspellings)) {
152
            $name = static::$misspellings[$name];
153
        }
154
155
        // Само склонение
156 36
        if (!in_array($name, static::$abbreviations, true)) {
157 34
            switch (S::slice($name, -2)) {
158
                // Нижний, Русский
159 34
                case 'ий':
160 3
                    $prefix = S::name(S::slice($name, 0, -2));
161
                    return [
162 3
                        static::IMENIT => $prefix.'ий',
163 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
164 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
165 3
                        static::VINIT => $prefix.'ий',
166 3
                        static::TVORIT => $prefix.'им',
167 3
                        static::PREDLOJ => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
168
                    ];
169
170
                // Ростовская
171 33
                case 'ая':
172 1
                    $prefix = S::name(S::slice($name, 0, -2));
173
                    return [
174 1
                        static::IMENIT => $prefix.'ая',
175 1
                        static::RODIT => $prefix.'ой',
176 1
                        static::DAT => $prefix.'ой',
177 1
                        static::VINIT => $prefix.'ую',
178 1
                        static::TVORIT => $prefix.'ой',
179 1
                        static::PREDLOJ => $prefix.'ой',
180
                    ];
181
182
                // Россошь
183 32
                case 'шь':
184 1
                    $prefix = S::name(S::slice($name, 0, -1));
185
                    return [
186 1
                        static::IMENIT => $prefix.'ь',
187 1
                        static::RODIT => $prefix.'и',
188 1
                        static::DAT => $prefix.'и',
189 1
                        static::VINIT => $prefix.'ь',
190 1
                        static::TVORIT => $prefix.'ью',
191 1
                        static::PREDLOJ => $prefix.'и',
192
                    ];
193
194
                // Грозный, Благодарный
195 31
                case 'ый':
196 2
                    $prefix = S::name(S::slice($name, 0, -2));
197
                    return [
198 2
                        static::IMENIT => $prefix.'ый',
199 2
                        static::RODIT => $prefix.'ого',
200 2
                        static::DAT => $prefix.'ому',
201 2
                        static::VINIT => $prefix.'ый',
202 2
                        static::TVORIT => $prefix.'ым',
203 2
                        static::PREDLOJ => $prefix.'ом',
204
                    ];
205
206
                // Ставрополь, Ярославль, Электросталь
207 29
                case 'ль':
208 2
                    $prefix = S::name(S::slice($name, 0, -1));
209
210 2 View Code Duplication
                    if ($name === 'электросталь')
211
                        return [
212 1
                            static::IMENIT => $prefix.'ь',
213 1
                            static::RODIT => $prefix.'и',
214 1
                            static::DAT => $prefix.'и',
215 1
                            static::VINIT => $prefix.'ь',
216 1
                            static::TVORIT => $prefix.'ью',
217 1
                            static::PREDLOJ => $prefix.'и',
218
                        ];
219
220
                    return [
221 1
                        static::IMENIT => $prefix.'ь',
222 1
                        static::RODIT => $prefix.'я',
223 1
                        static::DAT => $prefix.'ю',
224 1
                        static::VINIT => $prefix.'ь',
225 1
                        static::TVORIT => $prefix.'ем',
226 1
                        static::PREDLOJ => $prefix.'е',
227
                    ];
228
229
                // Тверь, Анадырь
230 27
                case 'рь':
231 2
                    $prefix = S::name(S::slice($name, 0, -1));
232 2
                    $last_vowel = S::slice($prefix, -2, -1);
233
                    return [
234 2
                        static::IMENIT => $prefix . 'ь',
235 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
236 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
237 2
                        static::VINIT => $prefix . 'ь',
238 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
239 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
240
                    ];
241
242
                // Березники, Ессентуки
243 25
                case 'ки':
244 2
                    $prefix = S::name(S::slice($name, 0, -1));
245
                    return [
246 2
                        static::IMENIT => $prefix . 'и',
247 2
                        static::RODIT => $name == 'луки' ? $prefix : $prefix . 'ов',
248 2
                        static::DAT => $prefix . 'ам',
249 2
                        static::VINIT => $prefix . 'и',
250 2
                        static::TVORIT => $prefix . 'ами',
251 2
                        static::PREDLOJ => $prefix . 'ах',
252
                    ];
253
254
                // Пермь, Кемь
255 24
                case 'мь':
256 1
                    $prefix = S::name(S::slice($name, 0, -1));
257
                    return [
258 1
                        static::IMENIT => $prefix . 'ь',
259 1
                        static::RODIT => $prefix . 'и',
260 1
                        static::DAT => $prefix . 'и',
261 1
                        static::VINIT => $prefix . 'ь',
262 1
                        static::TVORIT => $prefix . 'ью',
263 1
                        static::PREDLOJ => $prefix . 'и',
264
                    ];
265
266
                // Рязань, Назрань
267 23
                case 'нь':
268 1
                    $prefix = S::name(S::slice($name, 0, -1));
269
                    return [
270 1
                        static::IMENIT => $prefix . 'ь',
271 1
                        static::RODIT => $prefix . 'и',
272 1
                        static::DAT => $prefix . 'и',
273 1
                        static::VINIT => $prefix . 'ь',
274 1
                        static::TVORIT => $prefix . 'ью',
275 1
                        static::PREDLOJ => $prefix . 'и',
276
                    ];
277
278
                // Набережные
279 22
                case 'ые':
280 1
                    $prefix = S::name(S::slice($name, 0, -1));
281
                    return [
282 1
                        static::IMENIT => $prefix . 'е',
283 1
                        static::RODIT => $prefix . 'х',
284 1
                        static::DAT => $prefix . 'м',
285 1
                        static::VINIT => $prefix . 'е',
286 1
                        static::TVORIT => $prefix . 'ми',
287 1
                        static::PREDLOJ => $prefix . 'х',
288
                    ];
289
290
                // Челны
291 22
                case 'ны':
292 1
                    $prefix = S::name(S::slice($name, 0, -1));
293
                    return [
294 1
                        static::IMENIT => $prefix . 'ы',
295 1
                        static::RODIT => $prefix . 'ов',
296 1
                        static::DAT => $prefix . 'ам',
297 1
                        static::VINIT => $prefix . 'ы',
298 1
                        static::TVORIT => $prefix . 'ами',
299 1
                        static::PREDLOJ => $prefix . 'ах',
300
                    ];
301
302
                // Великие
303 21
                case 'ие':
304 1
                    $prefix = S::name(S::slice($name, 0, -1));
305
                    return [
306 1
                        static::IMENIT => $prefix.'е',
307 1
                        static::RODIT => $prefix.'х',
308 1
                        static::DAT => $prefix.'м',
309 1
                        static::VINIT => $prefix.'е',
310 1
                        static::TVORIT => $prefix.'ми',
311 1
                        static::PREDLOJ => $prefix.'х',
312
                    ];
313
314
                // Керчь
315 20
                case 'чь':
316 1
                    $prefix = S::name(S::slice($name, 0, -1));
317
                    return [
318 1
                        static::IMENIT => $prefix.'ь',
319 1
                        static::RODIT => $prefix.'и',
320 1
                        static::DAT => $prefix.'и',
321 1
                        static::VINIT => $prefix.'ь',
322 1
                        static::TVORIT => $prefix.'ью',
323 1
                        static::PREDLOJ => $prefix.'и',
324
                    ];
325
            }
326
327 19
            switch (S::slice($name, -1)) {
328 19
                case 'р':
329
                    // Бор
330
                    $prefix = S::name(S::slice($name, 0, -1));
331
                    return [
332
                        static::IMENIT => $prefix.'р',
333
                        static::RODIT => $prefix.'ра',
334
                        static::DAT => $prefix.'ру',
335
                        static::VINIT => $prefix.'р',
336
                        static::TVORIT => $prefix.'ром',
337
                        static::PREDLOJ => $prefix.'ру',
338
                    ];
339
340 19 View Code Duplication
                case 'ы':
341
                    // Чебоксары, Шахты
342 1
                    $prefix = S::name(S::slice($name, 0, -1));
343
                    return [
344 1
                        static::IMENIT => $prefix.'ы',
345 1
                        static::RODIT => $prefix,
346 1
                        static::DAT => $prefix.'ам',
347 1
                        static::VINIT => $prefix.'ы',
348 1
                        static::TVORIT => $prefix.'ами',
349 1
                        static::PREDLOJ => $prefix.'ах',
350
                    ];
351
352 18
                case 'я':
353
                    // Азия
354 1
                    $prefix = S::name(S::slice($name, 0, -1));
355
                    return [
356 1
                        static::IMENIT => S::name($name),
357 1
                        static::RODIT => $prefix.'и',
358 1
                        static::DAT => $prefix.'и',
359 1
                        static::VINIT => $prefix.'ю',
360 1
                        static::TVORIT => $prefix.'ей',
361 1
                        static::PREDLOJ => $prefix.'и',
362
                    ];
363
364 17 View Code Duplication
                case 'а':
365
                    // Москва, Рига
366 5
                    $prefix = S::name(S::slice($name, 0, -1));
367
                    return [
368 5
                        static::IMENIT => $prefix.'а',
369 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
370 5
                        static::DAT => $prefix.'е',
371 5
                        static::VINIT => $prefix.'у',
372 5
                        static::TVORIT => $prefix.'ой',
373 5
                        static::PREDLOJ => $prefix.'е',
374
                    ];
375
376 12
                case 'й':
377
                    // Ишимбай
378 2
                    $prefix = S::name(S::slice($name, 0, -1));
379
                    return [
380 2
                        static::IMENIT => $prefix . 'й',
381 2
                        static::RODIT => $prefix . 'я',
382 2
                        static::DAT => $prefix . 'ю',
383 2
                        static::VINIT => $prefix . 'й',
384 2
                        static::TVORIT => $prefix . 'ем',
385 2
                        static::PREDLOJ => $prefix . 'е',
386
                    ];
387
            }
388
389 10
            if (static::isConsonant(S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
390 9
                $runaway_vowels_list = static::getRunAwayVowelsList();
391
392
                // if run-away vowel in name
393 9
                if (isset($runaway_vowels_list[$name])) {
394 3
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
395 3
                    $prefix = S::name(S::slice($name, 0, $runaway_vowel_offset) . S::slice($name, $runaway_vowel_offset + 1));
396
                } else {
397 6
                    $prefix = S::name($name);
398
                }
399
400
                // Париж, Валаам, Киев
401
                return [
402 9
                    static::IMENIT => S::name($name),
403 9
                    static::RODIT => $prefix . 'а',
404 9
                    static::DAT => $prefix . 'у',
405 9
                    static::VINIT => S::name($name),
406 9
                    static::TVORIT => $prefix . (static::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
407 9
                    static::PREDLOJ => $prefix . 'е',
408
                ];
409
            }
410
411
            // ов, ово, ёв, ёво, ев, ево, ...
412 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
413 2
            if ((in_array(S::slice($name, -1), ['е', 'о'], true) && in_array(S::slice($name, -3, -1), $suffixes, true)) || in_array(S::slice($name, -2), $suffixes, true)) {
414
                // ово, ёво, ...
415 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
416
                    $prefix = S::name(S::slice($name, 0, -1));
417
                }
418
                // ов, её, ...
419 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
420 1
                    $prefix = S::name($name);
421
                }
422
                return [
423 1
                    static::IMENIT => S::name($name),
424 1
                    static::RODIT => $prefix.'а',
425 1
                    static::DAT => $prefix.'у',
426 1
                    static::VINIT => S::name($name),
427 1
                    static::TVORIT => $prefix.'ым',
428 1
                    static::PREDLOJ => $prefix.'е',
429
                ];
430
            }
431
        }
432
433
        // if no rules matches or name is immutable
434 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
435 3
        return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], $name);
436
    }
437
438
    /**
439
     * Получение одной формы (падежа) названия.
440
     * @param string $name  Название
441
     * @param integer $case Падеж. Одна из констант \morphos\Russian\Cases или \morphos\Cases.
442
     * @see \morphos\Russian\Cases
443
     * @return string
444
     * @throws \Exception
445
     */
446
    public static function getCase($name, $case)
447
    {
448
        $case = static::canonizeCase($case);
449
        $forms = static::getCases($name);
450
        return $forms[$case];
451
    }
452
}
453