Completed
Push — master ( 6ac666...2075e0 )
by f
01:34
created

src/Russian/GeographicalNamesInflection.php (1 issue)

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
    /** @var string[]  */
14
    protected static $abbreviations = [
15
        'сша',
16
        'оаэ',
17
        'ссср',
18
        'юар',
19
    ];
20
21
    /** @var string[]  */
22
    protected static $delimiters = [
23
        ' ',
24
        '-на-',
25
        '-эль-',
26
        '-де-',
27
        '-сюр-',
28
        '-ан-',
29
        '-ла-',
30
        '-',
31
    ];
32
33
    /** @var string[]  */
34
    protected static $ovAbnormalExceptions = [
35
        'осташков',
36
    ];
37
38
    /** @var string[]  */
39
    protected static $immutableNames = [
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
    /** @var string[]  */
78
    protected static $runawayVowelsExceptions = [
79
        'торжо*к',
80
        'волоче*к',
81
        'орё*л',
82
        'египе*т',
83
        'лунине*ц',
84
        'городо*к',
85
        'новогрудо*к',
86
        'острове*ц',
87
        'черепове*ц',
88
    ];
89
90
    /**
91
     * @var string[]
92
     * @phpstan-var array<string, string>
93
     */
94
    protected static $misspellings = [
95
        'орел' => 'орёл',
96
        'рублево' => 'рублёво',
97
    ];
98
99
    /**
100
     * @return int[]|false[]
101
     */
102 13 View Code Duplication
    protected static function getRunAwayVowelsList()
103
    {
104 13
        $runawayVowelsNormalized = [];
105 13
        foreach (static::$runawayVowelsExceptions as $word) {
106 13
            $runawayVowelsNormalized[str_replace('*', '', $word)] = S::indexOf($word, '*') - 1;
107
        }
108 13
        return $runawayVowelsNormalized;
109
    }
110
111
    /**
112
     * Проверяет, склоняемо ли название
113
     * @param string $name Название
114
     * @return bool
115
     */
116 2
    public static function isMutable($name)
117
    {
118 2
        $name = S::lower($name);
119
120
        // // ends with 'ы' or 'и': plural form
121
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
122
        //     return false;
123
124 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableNames, true)) {
125 2
            return false;
126
        }
127
128
        if (strpos($name, ' ') !== false) {
129
            // explode() is not applicable because Geographical unit may have few words
130
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
131
            $last_part = S::slice($name,
132
                S::findLastPosition($name, ' ') + 1);
133
134
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
135
            // N область, N край, N район, N волость
136
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
137
                || in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
138
                return true;
139
            }
140
141
            // пгт N
142
            if ($first_part === 'пгт')
143
                return false;
144
        }
145
146
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
147
        if (in_array(S::slice($name, -1), ['е', 'о'], true)
148
            && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
149
            return false;
150
        }
151
        return true;
152
    }
153
154
    /**
155
     * Получение всех форм названия
156
     * @param string $name
157
     * @return string[]
158
     * @phpstan-return array<string, string>
159
     * @throws \Exception
160
     */
161 47
    public static function getCases($name)
162
    {
163 47
        $name = S::lower($name);
164
165
        // Проверка на неизменяемость и сложное название
166 47
        if (in_array($name, static::$immutableNames, true)) {
167 2
            return array_fill_keys(
168 2
                [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE]
169 2
                , S::name($name));
170
        }
171
172 47
        if (strpos($name, ' ') !== false) {
173 9
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
174
            // город N, село N, хутор N, пгт N
175 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
176 3
                if ($first_part === 'пгт')
177
                    return array_fill_keys(
178
                        [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
179
                        'пгт '.S::name(S::slice($name, 4)));
180
181 3
                if ($first_part === 'республика') {
182 1
                    $prefix = array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part));
183
                } else {
184 2
                    $prefix = NounDeclension::getCases($first_part);
185
                }
186 3
                $prefix[Cases::LOCATIVE] = $prefix[Cases::PREDLOJ];
187
188 3
                return static::composeCasesFromWords([$prefix,
189 3
                    array_fill_keys(
190 3
                        array_merge(static::getAllCases(), [\morphos\Russian\Cases::LOCATIVE]),
191 3
                        S::name(S::slice($name, S::length($first_part) + 1)))
192
                ]);
193
            }
194
195 6
            $last_part = S::slice($name,
196 6
                S::findLastPosition($name, ' ') + 1);
197
            // N область, N край
198 6
            if (in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
199 2
                $last_part_cases = NounDeclension::getCases($last_part);
200 2
                $last_part_cases[Cases::LOCATIVE] = $last_part_cases[Cases::PREDLOJ];
201 2
                return static::composeCasesFromWords(
202
                    [
203 2
                        static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))),
204 2
                        $last_part_cases,
205
                    ]);
206
            }
207
        }
208
209
        // Сложное название с разделителем
210 44
        foreach (static::$delimiters as $delimiter) {
211 44
            if (strpos($name, $delimiter) !== false) {
212 7
                $parts = explode($delimiter, $name);
213 7
                $result = [];
214 7
                foreach ($parts as $i => $part) {
215 7
                    $result[$i] = static::getCases($part);
216
                }
217 44
                return static::composeCasesFromWords($result, $delimiter);
218
            }
219
        }
220
221
        // Исправление ошибок
222 44
        if (array_key_exists($name, static::$misspellings)) {
223
            $name = static::$misspellings[$name];
224
        }
225
226
        // Само склонение
227 44
        if (!in_array($name, static::$abbreviations, true)) {
228 42
            switch (S::slice($name, -2)) {
229
                // Нижний, Русский
230 42
                case 'ий':
231 3
                    $prefix = S::name(S::slice($name, 0, -2));
232
                    return [
233 3
                        static::IMENIT => $prefix.'ий',
234 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
235 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
236 3
                        static::VINIT => $prefix.'ий',
237 3
                        static::TVORIT => $prefix.'им',
238 3
                        static::PREDLOJ => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
239 3
                        static::LOCATIVE => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
240
                    ];
241
242
                // Ростовская
243 41
                case 'ая':
244 1
                    $prefix = S::name(S::slice($name, 0, -2));
245
                    return [
246 1
                        static::IMENIT => $prefix.'ая',
247 1
                        static::RODIT => $prefix.'ой',
248 1
                        static::DAT => $prefix.'ой',
249 1
                        static::VINIT => $prefix.'ую',
250 1
                        static::TVORIT => $prefix.'ой',
251 1
                        static::PREDLOJ => $prefix.'ой',
252 1
                        static::LOCATIVE => $prefix.'ой',
253
                    ];
254
255
                // Нижняя, Верхняя, Средняя
256 40
                case 'яя':
257
                    $prefix = S::name(S::slice($name, 0, -2));
258
                    return [
259
                        static::IMENIT => $prefix.'яя',
260
                        static::RODIT => $prefix.'ей',
261
                        static::DAT => $prefix.'ей',
262
                        static::VINIT => $prefix.'юю',
263
                        static::TVORIT => $prefix.'ей',
264
                        static::PREDLOJ => $prefix.'ей',
265
                        static::LOCATIVE => $prefix.'ей',
266
                    ];
267
268
                // Россошь
269 40
                case 'шь':
270
                // Пермь, Кемь
271 39
                case 'мь':
272
                // Рязань, Назрань
273 38
                case 'нь':
274
                // Сысерть
275 37
                case 'ть':
276
                // Керчь
277 37
                case 'чь':
278 4
                    $prefix = S::name(S::slice($name, 0, -1));
279
                    return [
280 4
                        static::IMENIT => $prefix.'ь',
281 4
                        static::RODIT => $prefix.'и',
282 4
                        static::DAT => $prefix.'и',
283 4
                        static::VINIT => $prefix.'ь',
284 4
                        static::TVORIT => $prefix.'ью',
285 4
                        static::PREDLOJ => $prefix.'и',
286 4
                        static::LOCATIVE => $prefix.'и',
287
                    ];
288
289
                // Грозный, Благодарный
290 36
                case 'ый':
291 2
                    $prefix = S::name(S::slice($name, 0, -2));
292
                    return [
293 2
                        static::IMENIT => $prefix.'ый',
294 2
                        static::RODIT => $prefix.'ого',
295 2
                        static::DAT => $prefix.'ому',
296 2
                        static::VINIT => $prefix.'ый',
297 2
                        static::TVORIT => $prefix.'ым',
298 2
                        static::PREDLOJ => $prefix.'ом',
299 2
                        static::LOCATIVE => $prefix.'ом',
300
                    ];
301
302
                // Ставрополь, Ярославль, Электросталь
303 34
                case 'ль':
304 2
                    $prefix = S::name(S::slice($name, 0, -1));
305
306 2
                    if ($name === 'электросталь')
307
                        return [
308 1
                            static::IMENIT => $prefix.'ь',
309 1
                            static::RODIT => $prefix.'и',
310 1
                            static::DAT => $prefix.'и',
311 1
                            static::VINIT => $prefix.'ь',
312 1
                            static::TVORIT => $prefix.'ью',
313 1
                            static::PREDLOJ => $prefix.'и',
314 1
                            static::LOCATIVE => $prefix.'и',
315
                        ];
316
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 1
                        static::LOCATIVE => $prefix.'е',
325
                    ];
326
327
                // Адыгея, Чечня
328 32
                case 'ея':
329 31
                case 'ня':
330 1
                    $prefix = S::name(S::slice($name, 0, -1));
331
                    return [
332 1
                        static::IMENIT => S::name($name),
333 1
                        static::RODIT => $prefix.'е',
334 1
                        static::DAT => $prefix.'е',
335 1
                        static::VINIT => $prefix.'я',
336 1
                        static::TVORIT => $prefix.'ей',
337 1
                        static::PREDLOJ => $prefix.'е',
338 1
                        static::LOCATIVE => $prefix.'е',
339
                    ];
340
341
                // Тверь, Анадырь
342 31
                case 'рь':
343 2
                    $prefix = S::name(S::slice($name, 0, -1));
344 2
                    $last_vowel = S::slice($prefix, -2, -1);
345
                    return [
346 2
                        static::IMENIT => $prefix . 'ь',
347 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
348 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
349 2
                        static::VINIT => $prefix . 'ь',
350 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
351 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
352 2
                        static::LOCATIVE => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
353
                    ];
354
355
                // Березники, Ессентуки
356 29
                case 'ки':
357
                // Старые Дороги
358 28
                case 'ги':
359
                // Ушачи, Ивацевичи
360 28
                case 'чи':
361 3
                    $prefix = S::name(S::slice($name, 0, -1));
362
                    return [
363 3
                        static::IMENIT => $prefix . 'и',
364 3
                        static::RODIT => ($name === 'луки'
365 1
                            ? $prefix
366 2
                            : (S::slice($name, -2) === 'чи'
367 1
                                ? $prefix . 'ей'
368 3
                                : $prefix . 'ов')),
369 3
                        static::DAT => $prefix . 'ам',
370 3
                        static::VINIT => $prefix . 'и',
371 3
                        static::TVORIT => $prefix . 'ами',
372 3
                        static::PREDLOJ => $prefix . 'ах',
373 3
                        static::LOCATIVE => $prefix . 'ах',
374
                    ];
375
376
                // Набережные
377 27
                case 'ые':
378
                // Великие
379 27
                case 'ие':
380 2
                    $prefix = S::name(S::slice($name, 0, -1));
381
                    return [
382 2
                        static::IMENIT => $prefix . 'е',
383 2
                        static::RODIT => $prefix . 'х',
384 2
                        static::DAT => $prefix . 'м',
385 2
                        static::VINIT => $prefix . 'е',
386 2
                        static::TVORIT => $prefix . 'ми',
387 2
                        static::PREDLOJ => $prefix . 'х',
388 2
                        static::LOCATIVE => $prefix . 'х',
389
                    ];
390
391
                // Челны
392 26
                case 'ны':
393
                // Мосты
394 25
                case 'ты':
395
                // Столбцы
396 25
                case 'цы':
397 2
                    $prefix = S::name(S::slice($name, 0, -1));
398
                    return [
399 2
                        static::IMENIT => $prefix . 'ы',
400 2
                        static::RODIT => $prefix . 'ов',
401 2
                        static::DAT => $prefix . 'ам',
402 2
                        static::VINIT => $prefix . 'ы',
403 2
                        static::TVORIT => $prefix . 'ами',
404 2
                        static::PREDLOJ => $prefix . 'ах',
405 2
                        static::LOCATIVE => $prefix . 'ах',
406
                    ];
407
408
                // Глубокое
409 24
                case 'ое':
410 1
                    $prefix = S::name(S::slice($name, 0, -2));
411
                    return [
412 1
                        static::IMENIT => $prefix.'ое',
413 1
                        static::RODIT => $prefix.'ого',
414 1
                        static::DAT => $prefix.'ому',
415 1
                        static::VINIT => $prefix.'ое',
416 1
                        static::TVORIT => $prefix.'им',
417 1
                        static::PREDLOJ => $prefix.'ом',
418 1
                        static::LOCATIVE => $prefix.'ом',
419
                    ];
420
421
            }
422
423 23
            switch (S::slice($name, -1)) {
424 23
                case 'р':
425
                    // Бор
426
                    $prefix = S::name(S::slice($name, 0, -1));
427
                    return [
428
                        static::IMENIT => $prefix.'р',
429
                        static::RODIT => $prefix.'ра',
430
                        static::DAT => $prefix.'ру',
431
                        static::VINIT => $prefix.'р',
432
                        static::TVORIT => $prefix.'ром',
433
                        static::PREDLOJ => $prefix.'ре',
434
                        static::LOCATIVE => $prefix.'ру',
435
                    ];
436
437 23
                case 'ы':
438
                    // Чебоксары, Шахты
439 1
                    $prefix = S::name(S::slice($name, 0, -1));
440
                    return [
441 1
                        static::IMENIT => $prefix.'ы',
442 1
                        static::RODIT => $prefix,
443 1
                        static::DAT => $prefix.'ам',
444 1
                        static::VINIT => $prefix.'ы',
445 1
                        static::TVORIT => $prefix.'ами',
446 1
                        static::PREDLOJ => $prefix.'ах',
447 1
                        static::LOCATIVE => $prefix.'ах',
448
                    ];
449
450 22
                case 'я':
451
                    // Азия
452 1
                    $prefix = S::name(S::slice($name, 0, -1));
453
                    return [
454 1
                        static::IMENIT => S::name($name),
455 1
                        static::RODIT => $prefix.'и',
456 1
                        static::DAT => $prefix.'и',
457 1
                        static::VINIT => $prefix.'ю',
458 1
                        static::TVORIT => $prefix.'ей',
459 1
                        static::PREDLOJ => $prefix.'и',
460 1
                        static::LOCATIVE => $prefix.'и',
461
                    ];
462
463 21
                case 'а':
464
                    // Москва, Рига
465 5
                    $prefix = S::name(S::slice($name, 0, -1));
466
                    return [
467 5
                        static::IMENIT => $prefix.'а',
468 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) || static::isHissingConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
469 5
                        static::DAT => $prefix.'е',
470 5
                        static::VINIT => $prefix.'у',
471 5
                        static::TVORIT => $prefix.'ой',
472 5
                        static::PREDLOJ => $prefix.'е',
473 5
                        static::LOCATIVE => $prefix.'е',
474
                    ];
475
476 16
                case 'й':
477
                    // Ишимбай
478 2
                    $prefix = S::name(S::slice($name, 0, -1));
479
                    return [
480 2
                        static::IMENIT => $prefix . 'й',
481 2
                        static::RODIT => $prefix . 'я',
482 2
                        static::DAT => $prefix . 'ю',
483 2
                        static::VINIT => $prefix . 'й',
484 2
                        static::TVORIT => $prefix . 'ем',
485 2
                        static::PREDLOJ => $prefix . 'е',
486 2
                        static::LOCATIVE => $prefix . 'е',
487
                    ];
488
            }
489
490 14
            if (static::isConsonant($last_char = S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
491 13
                $runaway_vowels_list = static::getRunAwayVowelsList();
492
493
                // if run-away vowel in name
494 13
                if (isset($runaway_vowels_list[$name])) {
495 4
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
496 4
                    $prefix = S::name(S::slice($name, 0, $runaway_vowel_offset) . S::slice($name, $runaway_vowel_offset + 1));
497
                } else {
498 9
                    $prefix = S::name($name);
499
                }
500
501
                // Париж, Валаам, Киев
502
                return [
503 13
                    static::IMENIT => S::name($name),
504 13
                    static::RODIT => $prefix . 'а',
505 13
                    static::DAT => $prefix . 'у',
506 13
                    static::VINIT => S::name($name),
507 13
                    static::TVORIT => $prefix . (
508 13
                        static::isVelarConsonant(S::slice($name, -2, -1))
509 13
                            || static::isHissingConsonant($last_char)
510 13
                        ? 'ем' : 'ом'),
511 13
                    static::PREDLOJ => $prefix . 'е',
512 13
                    static::LOCATIVE => $prefix.($name === 'крым' ? 'у' : 'е'),
513
                ];
514
            }
515
516
            // ов, ово, ёв, ёво, ев, ево, ...
517 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
518 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)) {
519
                // ово, ёво, ...
520 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
521
                    $prefix = S::name(S::slice($name, 0, -1));
522
                }
523
                // ов, её, ...
524 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
525 1
                    $prefix = S::name($name);
526
                } else {
527
                    $prefix = '';
528
                }
529
530
                return [
0 ignored issues
show
The expression return array(static::IME...IVE => $prefix . 'е'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
531 1
                    static::IMENIT => S::name($name),
532 1
                    static::RODIT => $prefix.'а',
533 1
                    static::DAT => $prefix.'у',
534 1
                    static::VINIT => S::name($name),
535 1
                    static::TVORIT => $prefix.'ым',
536 1
                    static::PREDLOJ => $prefix.'е',
537 1
                    static::LOCATIVE => $prefix.'е',
538
                ];
539
            }
540
        }
541
542
        // if no rules matches or name is immutable
543 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
544 3
        return array_fill_keys(
545 3
            [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
546 3
        $name);
547
    }
548
549
    /**
550
     * Получение одной формы (падежа) названия.
551
     * @param string $name  Название
552
     * @param string $case Падеж. Одна из констант {@see \morphos\Russian\Cases} или {@see \morphos\Cases}.
553
     * @see \morphos\Russian\Cases
554
     * @return string
555
     * @throws \Exception
556
     */
557
    public static function getCase($name, $case)
558
    {
559
        $case = static::canonizeCase($case);
560
        $forms = static::getCases($name);
561
        return $forms[$case];
562
    }
563
}
564