Completed
Push — master ( 7b0904...670e33 )
by f
14:18
created

NounDeclension::getDeclension()   B

Complexity

Conditions 11
Paths 4

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 11

Importance

Changes 0
Metric Value
cc 11
nc 4
nop 2
dl 0
loc 18
ccs 5
cts 5
cp 1
crap 11
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace morphos\Russian;
3
4
use morphos\BaseInflection;
5
use morphos\Gender;
6
use morphos\S;
7
use RuntimeException;
8
9
/**
10
 * Rules are from http://morpher.ru/Russian/Noun.aspx
11
 */
12
class NounDeclension extends BaseInflection implements Cases, Gender
13
{
14
    use RussianLanguage, CasesHelper;
15
16
    const FIRST_DECLENSION = 1;
17
    const SECOND_DECLENSION = 2;
18
    const THIRD_DECLENSION = 3;
19
20
    /** @var string[] */
21
    public static $immutableWords = [
22
        // валюты
23
        'евро', 'пенни', 'песо', 'сентаво',
24
25
        // на а
26
        'боа', 'бра', 'фейхоа', 'амплуа', 'буржуа',
27
        // на о
28
        'манго', 'какао', 'кино', 'трюмо', 'пальто', 'бюро', 'танго', 'вето', 'бунгало', 'сабо', 'авокадо', 'депо', 'панно',
29
        // на у
30
        'зебу', 'кенгуру', 'рагу', 'какаду', 'шоу',
31
        // на е
32
        'шимпанзе', 'конферансье', 'атташе', 'колье', 'резюме', 'пенсне', 'кашне', 'протеже', 'коммюнике', 'драже', 'суфле', 'пюре', 'купе', 'фойе', 'шоссе', 'крупье',
33
        // на и
34
        'такси', 'жалюзи', 'шасси', 'алиби', 'киви', 'иваси', 'регби', 'конфетти', 'колибри', 'жюри', 'пенальти', 'рефери', 'кольраби',
35
        // на э
36
        'каноэ', 'алоэ',
37
        // на ю
38
        'меню', 'парвеню', 'авеню', 'дежавю', 'инженю', 'барбекю', 'интервью',
39
    ];
40
41
    /**
42
     * These words has 2 declension type.
43
     * @var string[]|string[][]
44
     */
45
    protected static $abnormalExceptions = [
46
        'бремя',
47
        'вымя',
48
        'темя',
49
        'пламя',
50
        'стремя',
51
        'пламя',
52
        'время',
53
        'знамя',
54
        'имя',
55
        'племя',
56
        'семя',
57
        'путь' => ['путь', 'пути', 'пути', 'путь', 'путем', 'пути'],
58
        'дитя' => ['дитя', 'дитяти', 'дитяти', 'дитя', 'дитятей', 'дитяти'],
59
    ];
60
61
    /** @var string[]  */
62
    protected static $masculineWithSoft = [
63
        'автослесарь',
64
        'библиотекарь',
65
        'водитель',
66
        'воспитатель',
67
        'врач',
68
        'выхухоль',
69
        'гвоздь',
70
        'делопроизводитель',
71
        'день',
72
        'дождь',
73
        'заместитель',
74
        'зверь',
75
        'камень',
76
        'конь',
77
        'конь',
78
        'корень',
79
        'лось',
80
        'медведь',
81
        'модуль',
82
        'олень',
83
        'парень',
84
        'пекарь',
85
        'пельмень',
86
        'пень',
87
        'председатель',
88
        'представитель',
89
        'преподаватель',
90
        'продавец',
91
        'производитель',
92
        'путь',
93
        'рояль',
94
        'рубль',
95
        'руководитель',
96
        'секретарь',
97
        'слесарь',
98
        'строитель',
99
        'табель',
100
        'токарь',
101
        'трутень',
102
        'тюлень',
103
        'учитель',
104
        'циркуль',
105
        'шампунь',
106
        'шкворень',
107
        'юань',
108
        'ячмень',
109
    ];
110
111
    /** @var string[]  */
112
    protected static $masculineWithSoftAndRunAwayVowels = [
113
        'санузел',
114
    ];
115
116
    /** @var string[] */
117
    public static $runawayVowelsExceptions = [
118
        'глото*к',
119
        'де*нь',
120
        'каме*нь',
121 43
        'коре*нь',
122
        'паре*нь',
123 43
        'пе*нь',
124 43
        'песе*ц',
125 43
        'писе*ц',
126
        'санузе*л',
127
        'труте*нь',
128
    ];
129
130
    /**
131
     * Проверка, изменяемое ли слово.
132
     * @param string $word Слово для проверки
133
     * @param bool $animateness Признак одушевленности
134
     * @return bool
135 8
     */
136
    public static function isMutable($word, $animateness = false)
0 ignored issues
show
Unused Code introduced by
The parameter $animateness is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
137 8
    {
138 8
        $word = S::lower($word);
139
        if (in_array(S::slice($word, -1), ['у', 'и', 'е', 'о', 'ю'], true) || in_array($word, static::$immutableWords, true)) {
140 8
            return false;
141 2
        }
142
        return true;
143 6
    }
144 6
145 3
    /**
146
     * Определение рода существительного.
147 3
     * @param string $word
148
     * @return string
149
     */
150
    public static function detectGender($word)
151
    {
152
    	$word = S::lower($word);
153
    	$last = S::slice($word, -1);
154
		// пытаемся угадать род объекта, хотя бы примерно, чтобы правильно склонять
155
		if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у'], true))
156 199
			return static::NEUTER;
157
158 199
		if (in_array($last, ['а', 'я'], true) ||
159 199
			($last == 'ь' && !in_array($word, static::$masculineWithSoft, true)))
160 199
			return static::FEMALE;
161 3
162
		return static::MALE;
163
    }
164 196
165 64
    /**
166 147
     * Определение склонения (по школьной программе) существительного.
167 41
     * @param string $word
168 147
     * @param bool $animateness
169 138
     * @return int
170
     */
171 9
    public static function getDeclension($word, $animateness = false)
0 ignored issues
show
Unused Code introduced by
The parameter $animateness is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
172
    {
173
        $word = S::lower($word);
174
        $last = S::slice($word, -1);
175
        if (isset(static::$abnormalExceptions[$word]) || in_array($word, static::$abnormalExceptions, true)) {
176
            return 2;
177
        }
178
179
        if (in_array($last, ['а', 'я'], true) && S::slice($word, -2) != 'мя') {
180
            return 1;
181
        } elseif (static::isConsonant($last) || in_array($last, ['о', 'е', 'ё'], true)
182 109
            || ($last == 'ь' && static::isConsonant(S::slice($word, -2, -1)) && !static::isHissingConsonant(S::slice($word, -2, -1))
183
                && (in_array($word, static::$masculineWithSoft, true)) /*|| in_array($word, static::$masculineWithSoftAndRunAwayVowels, true)*/)) {
184 109
            return 2;
185
        } else {
186
            return 3;
187 109
        }
188 8
    }
189
190
    /**
191
     * Получение слова во всех 6 падежах.
192 101
     * @param string $word
193
     * @param bool $animateness Признак одушевлённости
194 3
     * @return string[]
195 3
     * @phpstan-return array<string, string>
196 3
     */
197 3
    public static function getCases($word, $animateness = false)
198 3
    {
199 3
        $word = S::lower($word);
200
201
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
202
        if (static::isAdjectiveNoun($word)) {
203 98
            return static::declinateAdjective($word, $animateness);
204 2
        }
205
206
        // Субстантивное склонение (существительные)
207 96 View Code Duplication
        if (in_array($word, static::$immutableWords, true)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
208 1
            return [
209
                static::IMENIT => $word,
210 1
                static::RODIT => $word,
211 1
                static::DAT => $word,
212 1
                static::VINIT => $word,
213 1
                static::TVORIT => $word,
214 1
                static::PREDLOJ => $word,
215 1
            ];
216
        }
217
218 View Code Duplication
        if (isset(static::$abnormalExceptions[$word])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
219 95
            return array_combine([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], static::$abnormalExceptions[$word]);
220 95
        }
221 25
222 72
        if (in_array($word, static::$abnormalExceptions, true)) {
223 68
            $prefix = S::slice($word, 0, -1);
224 4
            return [
225 4
                static::IMENIT => $word,
226
                static::RODIT => $prefix.'ени',
227
                static::DAT => $prefix.'ени',
228
                static::VINIT => $word,
229
                static::TVORIT => $prefix.'енем',
230
                static::PREDLOJ => $prefix.'ени',
231
            ];
232
        }
233
234
        switch (static::getDeclension($word)) {
235
            case static::FIRST_DECLENSION:
236
                return static::declinateFirstDeclension($word);
237 25
            case static::SECOND_DECLENSION:
238
                return static::declinateSecondDeclension($word, $animateness);
239 25
            case static::THIRD_DECLENSION:
240 25
                return static::declinateThirdDeclension($word);
241 25
242 25
            default: throw new RuntimeException('Unreachable');
243
        }
244 25
    }
245
246
    /**
247
     * Получение всех форм слова первого склонения.
248 25
     * @param string $word
249
     * @return string[]
250
     * @phpstan-return array<string, string>
251 25
     */
252
    public static function declinateFirstDeclension($word)
253
    {
254 25
        $word = S::lower($word);
255
        $prefix = S::slice($word, 0, -1);
256
        $last = S::slice($word, -1);
257 25
        $soft_last = static::checkLastConsonantSoftness($word);
258
        $forms =  [
259
            Cases::IMENIT => $word,
260 25
        ];
261
262
        // RODIT
263
        $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), ['г', 'к', 'х'], true)), $prefix.'и', $prefix.'ы');
264
265
        // DAT
266
        $forms[Cases::DAT] = static::getPredCaseOf12Declensions($word, $last, $prefix);
267
268
        // VINIT
269 25
        $forms[Cases::VINIT] = static::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) !== 'ч', $prefix.'ю', $prefix.'у');
270 25
271
        // TVORIT
272
        if ($last === 'ь') {
273
            $forms[Cases::TVORIT] = $prefix.'ой';
274 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
275
            $forms[Cases::TVORIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой');
276
        }
277
278
        // 	if ($last == 'й' || (static::isConsonant($last) && !static::isHissingConsonant($last)) || static::checkLastConsonantSoftness($word))
279
        // 	$forms[Cases::TVORIT] = $prefix.'ей';
280 68
        // else
281
        // 	$forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
282 68
283 68
        // PREDLOJ the same as DAT
284 68
        $forms[Cases::PREDLOJ] = $forms[Cases::DAT];
285
        return $forms;
286 65
    }
287
288
    /**
289 23
     * Получение всех форм слова второго склонения.
290 19
     * @param string $word
291
     * @param bool $animateness
292 68
     * @return string[]
293
     * @phpstan-return array<string, string>
294 68
     */
295
    public static function declinateSecondDeclension($word, $animateness = false)
296 68
    {
297
        $word = S::lower($word);
298
        $last = S::slice($word, -1);
299
        $soft_last = $last === 'й'
300 68
            || (
301
                in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
302
                && (
303 68
                        (
304
                        static::isConsonant(S::slice($word, -2, -1))
305
                        && !static::isHissingConsonant(S::slice($word, -2, -1))
306 68
                        )
307 13
                    || S::slice($word, -2, -1) === 'и')
308
               );
309 55
        $prefix = static::getPrefixOfSecondDeclension($word, $last);
310
        $forms =  [
311
            Cases::IMENIT => $word,
312
        ];
313
314
        // RODIT
315
        $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
316
317
        // DAT
318
        $forms[Cases::DAT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
319 68
320 65
        // VINIT
321 68
        if (in_array($last, ['о', 'е', 'ё'], true)) {
322 6
            $forms[Cases::VINIT] = $word;
323 62
        } else {
324 24
            $forms[Cases::VINIT] = static::getVinitCaseByAnimateness($forms, $animateness);
325
        }
326 38
327
        // TVORIT
328
        // if ($last == 'ь')
329
        // 	$forms[Cases::TVORIT] = $prefix.'ом';
330 68
        // else if ($last == 'й' || (static::isConsonant($last) && !static::isHissingConsonant($last)))
331
        // 	$forms[Cases::TVORIT] = $prefix.'ем';
332 68
        // else
333
        // 	$forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
334
        if ((static::isHissingConsonant($last) && $last !== 'ш')
335
            || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) && static::isHissingConsonant(S::slice($word, -2, -1)))
336
            || ($last === 'ц' && S::slice($word, -2) !== 'ец')) {
337
            $forms[Cases::TVORIT] = $prefix.'ем';
338 View Code Duplication
        } elseif (in_array($last, ['й'/*, 'ч', 'щ'*/], true) || $soft_last) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
339
            $forms[Cases::TVORIT] = $prefix.'ем';
340
        } else {
341 4
            $forms[Cases::TVORIT] = $prefix.'ом';
342
        }
343 4
344 4
        // PREDLOJ
345
        $forms[Cases::PREDLOJ] = static::getPredCaseOf12Declensions($word, $last, $prefix);
346 4
347 4
        return $forms;
348 4
    }
349 4
350 4
    /**
351 4
     * Получение всех форм слова третьего склонения.
352
     * @param string $word
353
     * @return string[]
354
     * @phpstan-return array<string, string>
355
     */
356
    public static function declinateThirdDeclension($word)
357
    {
358
        $word = S::lower($word);
359
        $prefix = S::slice($word, 0, -1);
360
        return [
361
            Cases::IMENIT => $word,
362
            Cases::RODIT => $prefix.'и',
363 8
            Cases::DAT => $prefix.'и',
364
            Cases::VINIT => $word,
365 8
            Cases::TVORIT => $prefix.'ью',
366
            Cases::PREDLOJ => $prefix.'и',
367 8
        ];
368
    }
369 8
370 7
    /**
371
     * Склонение существительных, образованных от прилагательных и причастий.
372 2
     * Rules are from http://rusgram.narod.ru/1216-1231.html
373 2
     * @param string $word
374 2
     * @param bool $animateness
375 2
     * @return string[]
376 2
     * @phpstan-return array<string, string>
377 2
     */
378
    public static function declinateAdjective($word, $animateness)
0 ignored issues
show
Unused Code introduced by
The parameter $animateness is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
379
    {
380 6
        $prefix = S::slice($word, 0, -2);
381
382 1
        switch (S::slice($word, -2)) {
383 1
            // Male adjectives
384 1
            case 'ой':
385 1 View Code Duplication
            case 'ый':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
386 1
                return [
387 1
                    Cases::IMENIT => $word,
388
                    Cases::RODIT => $prefix.'ого',
389
                    Cases::DAT => $prefix.'ому',
390
                    Cases::VINIT => $word,
391 5
                    Cases::TVORIT => $prefix.'ым',
392 4
                    Cases::PREDLOJ => $prefix.'ом',
393 2
                ];
394
395 2 View Code Duplication
            case 'ий':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
396 2
                return [
397 2
                    Cases::IMENIT => $word,
398 2
                    Cases::RODIT => $prefix.'его',
399 2
                    Cases::DAT => $prefix.'ему',
400 2
                    Cases::VINIT => $prefix.'его',
401
                    Cases::TVORIT => $prefix.'им',
402
                    Cases::PREDLOJ => $prefix.'ем',
403
                ];
404 3
405 3
            // Neuter adjectives
406
            case 'ое':
407 3
            case 'ее':
408 3
                $prefix = S::slice($word, 0, -1);
409 3
                return [
410 3
                    Cases::IMENIT => $word,
411 3
                    Cases::RODIT => $prefix.'го',
412 3
                    Cases::DAT => $prefix.'му',
413
                    Cases::VINIT => $word,
414
                    Cases::TVORIT => S::slice($word, 0, -2).(S::slice($word, -2, -1) == 'о' ? 'ы' : 'и').'м',
415
                    Cases::PREDLOJ => $prefix.'м',
416
                ];
417
418
            // Female adjectives
419
            case 'ая':
420
                $ending = static::isHissingConsonant(S::slice($prefix, -1)) ? 'ей' : 'ой';
421
                return [
422
                    Cases::IMENIT => $word,
423
                    Cases::RODIT => $prefix.$ending,
424
                    Cases::DAT => $prefix.$ending,
425
                    Cases::VINIT => $prefix.'ую',
426
                    Cases::TVORIT => $prefix.$ending,
427 43
                    Cases::PREDLOJ => $prefix.$ending,
428
                ];
429 43
430 43
            default: throw new RuntimeException('Unreachable');
431 43
        }
432
    }
433
434
    /**
435
     * Получение одной формы слова (падежа).
436
     * @param string $word Слово
437
     * @param string $case Падеж
438
     * @param bool $animateness Признак одушевленности
439 103
     * @return string
440
     * @throws \Exception
441
     */
442 103
    public static function getCase($word, $case, $animateness = false)
443 7
    {
444 98
        $case = static::canonizeCase($case);
445 40
        $forms = static::getCases($word, $animateness);
446
        return $forms[$case];
447
    }
448 59
449 4
    /**
450
     * @param string $word
451
     * @param string $last
452 55
     * @return string
453 1
     */
454
    public static function getPrefixOfSecondDeclension($word, $last)
455 54
    {
456
        // слова с бегающей гласной в корне
457 103
        $runaway_vowels_list = static::getRunAwayVowelsList();
458
        if (isset($runaway_vowels_list[$word])) {
459
            $vowel_offset = $runaway_vowels_list[$word];
460
            $prefix = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1);
0 ignored issues
show
Security Bug introduced by
It seems like $vowel_offset defined by $runaway_vowels_list[$word] on line 459 can also be of type false; however, morphos\S::slice() does only seem to accept integer|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
461
        } elseif (in_array($last, ['о', 'е', 'ё', 'ь', 'й'], true)) {
462
            $prefix = S::slice($word, 0, -1);
463
        }
464
        // уменьшительные формы слов (котенок) и слова с суффиксом ок
465 View Code Duplication
        elseif (S::slice($word, -2) === 'ок' && S::length($word) > 3) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
466 91
            $prefix = S::slice($word, 0, -2) . 'к';
467
        }
468 91
        // слова с суффиксом бец
469 6 View Code Duplication
        elseif (S::slice($word, -3) === 'бец' && S::length($word) > 4) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
470
            $prefix = S::slice($word, 0, -3).'бц';
471
        } else {
472 6
            $prefix = $word;
473
        }
474
        return $prefix;
475 85
    }
476
477
    /**
478
     * @param string $word
479
     * @param string $last
480
     * @param string $prefix
481
     * @return string
482
     */
483
    public static function getPredCaseOf12Declensions($word, $last, $prefix)
484
    {
485
        if (in_array(S::slice($word, -2), ['ий', 'ие'], true)) {
486
            if ($last == 'ё') {
487
                return $prefix.'е';
488
            } else {
489
                return $prefix.'и';
490
            }
491
        } else {
492
            return $prefix.'е';
493
        }
494
    }
495
496
    /**
497
     * @return int[]|false[]
498
     */
499 View Code Duplication
    public static function getRunAwayVowelsList()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
500
    {
501
        $runawayVowelsNormalized = [];
502
        foreach (NounDeclension::$runawayVowelsExceptions as $word) {
503
            $runawayVowelsNormalized[str_replace('*', '', $word)] = S::indexOf($word, '*') - 1;
504
        }
505
        return $runawayVowelsNormalized;
506
    }
507
}
508