Completed
Push — master ( 761ec1...becd60 )
by f
18:57 queued 17:41
created

NounDeclension::declinateAdjective()   B

Complexity

Conditions 9
Paths 10

Size

Total Lines 55

Duplication

Lines 18
Ratio 32.73 %

Code Coverage

Tests 35
CRAP Score 9.0017

Importance

Changes 0
Metric Value
cc 9
nc 10
nop 2
dl 18
loc 55
ccs 35
cts 36
cp 0.9722
crap 9.0017
rs 7.4262
c 0
b 0
f 0

How to fix   Long Method   

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
        'коре*нь',
122
        'паре*нь',
123
        'пе*нь',
124
        'песе*ц',
125
        'писе*ц',
126
        'санузе*л',
127
        'труте*нь',
128
    ];
129
130
    /**
131
     * Проверка, изменяемое ли слово.
132
     * @param string $word Слово для проверки
133
     * @param bool $animateness Признак одушевленности
134
     * @return bool
135
     */
136 43
    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
    {
138 43
        $word = S::lower($word);
139 43
        if (in_array(S::slice($word, -1), ['у', 'и', 'е', 'о', 'ю'], true) || in_array($word, static::$immutableWords, true)) {
140 43
            return false;
141
        }
142
        return true;
143
    }
144
145
    /**
146
     * Определение рода существительного.
147
     * @param string $word
148
     * @return string
149
     */
150 8
    public static function detectGender($word)
151
    {
152 8
    	$word = S::lower($word);
153 8
    	$last = S::slice($word, -1);
154
		// пытаемся угадать род объекта, хотя бы примерно, чтобы правильно склонять
155 8
		if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у'], true))
156 2
			return static::NEUTER;
157
158 6
		if (in_array($last, ['а', 'я'], true) ||
159 6
			($last == 'ь' && !in_array($word, static::$masculineWithSoft, true)))
160 3
			return static::FEMALE;
161
162 3
		return static::MALE;
163
    }
164
165
    /**
166
     * Определение склонения (по школьной программе) существительного.
167
     * @param string $word
168
     * @param bool $animateness
169
     * @return int
170
     */
171 201
    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 201
        $word = S::lower($word);
174 201
        $last = S::slice($word, -1);
175 201
        if (isset(static::$abnormalExceptions[$word]) || in_array($word, static::$abnormalExceptions, true)) {
176 3
            return 2;
177
        }
178
179 198
        if (in_array($last, ['а', 'я'], true) && S::slice($word, -2) != 'мя') {
180 64
            return 1;
181 149
        } elseif (static::isConsonant($last) || in_array($last, ['о', 'е', 'ё'], true)
182 41
            || ($last == 'ь' && static::isConsonant(S::slice($word, -2, -1)) && !static::isHissingConsonant(S::slice($word, -2, -1))
183 149
                && (in_array($word, static::$masculineWithSoft, true)) /*|| in_array($word, static::$masculineWithSoftAndRunAwayVowels, true)*/)) {
184 140
            return 2;
185
        } else {
186 9
            return 3;
187
        }
188
    }
189
190
    /**
191
     * Получение слова во всех 6 падежах.
192
     * @param string $word
193
     * @param bool $animateness Признак одушевлённости
194
     * @return string[]
195
     * @phpstan-return array<string, string>
196
     */
197 110
    public static function getCases($word, $animateness = false)
198
    {
199 110
        $word = S::lower($word);
200
201
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
202 110
        if (static::isAdjectiveNoun($word)) {
203 8
            return static::declinateAdjective($word, $animateness);
204
        }
205
206
        // Субстантивное склонение (существительные)
207 102 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
            return [
209 3
                static::IMENIT => $word,
210 3
                static::RODIT => $word,
211 3
                static::DAT => $word,
212 3
                static::VINIT => $word,
213 3
                static::TVORIT => $word,
214 3
                static::PREDLOJ => $word,
215
            ];
216
        }
217
218 99 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 2
            return array_combine([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], static::$abnormalExceptions[$word]);
220
        }
221
222 97
        if (in_array($word, static::$abnormalExceptions, true)) {
223 1
            $prefix = S::slice($word, 0, -1);
224
            return [
225 1
                static::IMENIT => $word,
226 1
                static::RODIT => $prefix.'ени',
227 1
                static::DAT => $prefix.'ени',
228 1
                static::VINIT => $word,
229 1
                static::TVORIT => $prefix.'енем',
230 1
                static::PREDLOJ => $prefix.'ени',
231
            ];
232
        }
233
234 96
        switch (static::getDeclension($word)) {
235 96
            case static::FIRST_DECLENSION:
236 25
                return static::declinateFirstDeclension($word);
237 73
            case static::SECOND_DECLENSION:
238 69
                return static::declinateSecondDeclension($word, $animateness);
239 4
            case static::THIRD_DECLENSION:
240 4
                return static::declinateThirdDeclension($word);
241
242
            default: throw new RuntimeException('Unreachable');
243
        }
244
    }
245
246
    /**
247
     * Получение всех форм слова первого склонения.
248
     * @param string $word
249
     * @return string[]
250
     * @phpstan-return array<string, string>
251
     */
252 25
    public static function declinateFirstDeclension($word)
253
    {
254 25
        $word = S::lower($word);
255 25
        $prefix = S::slice($word, 0, -1);
256 25
        $last = S::slice($word, -1);
257 25
        $soft_last = static::checkLastConsonantSoftness($word);
258
        $forms =  [
259 25
            Cases::IMENIT => $word,
260
        ];
261
262
        // RODIT
263 25
        $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), ['г', 'к', 'х'], true)), $prefix.'и', $prefix.'ы');
264
265
        // DAT
266 25
        $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
271
        // TVORIT
272 25
        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 25
            $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
        // else
281
        // 	$forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
282
283
        // PREDLOJ the same as DAT
284 25
        $forms[Cases::PREDLOJ] = $forms[Cases::DAT];
285 25
        return $forms;
286
    }
287
288
    /**
289
     * Получение всех форм слова второго склонения.
290
     * @param string $word
291
     * @param bool $animateness
292
     * @return string[]
293
     * @phpstan-return array<string, string>
294
     */
295 69
    public static function declinateSecondDeclension($word, $animateness = false)
296
    {
297 69
        $word = S::lower($word);
298 69
        $last = S::slice($word, -1);
299 69
        $soft_last = $last === 'й'
300
            || (
301 66
                in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
302
                && (
303
                        (
304 23
                        static::isConsonant(S::slice($word, -2, -1))
305 19
                        && !static::isHissingConsonant(S::slice($word, -2, -1))
306
                        )
307 69
                    || S::slice($word, -2, -1) === 'и')
308
               );
309 69
        $prefix = static::getPrefixOfSecondDeclension($word, $last);
310
        $forms =  [
311 69
            Cases::IMENIT => $word,
312
        ];
313
314
        // RODIT
315 69
        $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
316
317
        // DAT
318 69
        $forms[Cases::DAT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
319
320
        // VINIT
321 69
        if (in_array($last, ['о', 'е', 'ё'], true)) {
322 13
            $forms[Cases::VINIT] = $word;
323
        } else {
324 56
            $forms[Cases::VINIT] = static::getVinitCaseByAnimateness($forms, $animateness);
325
        }
326
327
        // TVORIT
328
        // if ($last == 'ь')
329
        // 	$forms[Cases::TVORIT] = $prefix.'ом';
330
        // else if ($last == 'й' || (static::isConsonant($last) && !static::isHissingConsonant($last)))
331
        // 	$forms[Cases::TVORIT] = $prefix.'ем';
332
        // else
333
        // 	$forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
334 69
        if ((static::isHissingConsonant($last) && $last !== 'ш')
335 66
            || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) && static::isHissingConsonant(S::slice($word, -2, -1)))
336 69
            || ($last === 'ц' && S::slice($word, -2) !== 'ец')) {
337 6
            $forms[Cases::TVORIT] = $prefix.'ем';
338 63 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 24
            $forms[Cases::TVORIT] = $prefix.'ем';
340
        } else {
341 39
            $forms[Cases::TVORIT] = $prefix.'ом';
342
        }
343
344
        // PREDLOJ
345 69
        $forms[Cases::PREDLOJ] = static::getPredCaseOf12Declensions($word, $last, $prefix);
346
347 69
        return $forms;
348
    }
349
350
    /**
351
     * Получение всех форм слова третьего склонения.
352
     * @param string $word
353
     * @return string[]
354
     * @phpstan-return array<string, string>
355
     */
356 4
    public static function declinateThirdDeclension($word)
357
    {
358 4
        $word = S::lower($word);
359 4
        $prefix = S::slice($word, 0, -1);
360
        return [
361 4
            Cases::IMENIT => $word,
362 4
            Cases::RODIT => $prefix.'и',
363 4
            Cases::DAT => $prefix.'и',
364 4
            Cases::VINIT => $word,
365 4
            Cases::TVORIT => $prefix.'ью',
366 4
            Cases::PREDLOJ => $prefix.'и',
367
        ];
368
    }
369
370
    /**
371
     * Склонение существительных, образованных от прилагательных и причастий.
372
     * Rules are from http://rusgram.narod.ru/1216-1231.html
373
     * @param string $word
374
     * @param bool $animateness
375
     * @return string[]
376
     * @phpstan-return array<string, string>
377
     */
378 8
    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 8
        $prefix = S::slice($word, 0, -2);
381
382 8
        switch (S::slice($word, -2)) {
383
            // Male adjectives
384 8
            case 'ой':
385 7 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
                return [
387 2
                    Cases::IMENIT => $word,
388 2
                    Cases::RODIT => $prefix.'ого',
389 2
                    Cases::DAT => $prefix.'ому',
390 2
                    Cases::VINIT => $word,
391 2
                    Cases::TVORIT => $prefix.'ым',
392 2
                    Cases::PREDLOJ => $prefix.'ом',
393
                ];
394
395 6 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
                return [
397 1
                    Cases::IMENIT => $word,
398 1
                    Cases::RODIT => $prefix.'его',
399 1
                    Cases::DAT => $prefix.'ему',
400 1
                    Cases::VINIT => $prefix.'его',
401 1
                    Cases::TVORIT => $prefix.'им',
402 1
                    Cases::PREDLOJ => $prefix.'ем',
403
                ];
404
405
            // Neuter adjectives
406 5
            case 'ое':
407 4
            case 'ее':
408 2
                $prefix = S::slice($word, 0, -1);
409
                return [
410 2
                    Cases::IMENIT => $word,
411 2
                    Cases::RODIT => $prefix.'го',
412 2
                    Cases::DAT => $prefix.'му',
413 2
                    Cases::VINIT => $word,
414 2
                    Cases::TVORIT => S::slice($word, 0, -2).(S::slice($word, -2, -1) == 'о' ? 'ы' : 'и').'м',
415 2
                    Cases::PREDLOJ => $prefix.'м',
416
                ];
417
418
            // Female adjectives
419 3
            case 'ая':
420 3
                $ending = static::isHissingConsonant(S::slice($prefix, -1)) ? 'ей' : 'ой';
421
                return [
422 3
                    Cases::IMENIT => $word,
423 3
                    Cases::RODIT => $prefix.$ending,
424 3
                    Cases::DAT => $prefix.$ending,
425 3
                    Cases::VINIT => $prefix.'ую',
426 3
                    Cases::TVORIT => $prefix.$ending,
427 3
                    Cases::PREDLOJ => $prefix.$ending,
428
                ];
429
430
            default: throw new RuntimeException('Unreachable');
431
        }
432
    }
433
434
    /**
435
     * Получение одной формы слова (падежа).
436
     * @param string $word Слово
437
     * @param string $case Падеж
438
     * @param bool $animateness Признак одушевленности
439
     * @return string
440
     * @throws \Exception
441
     */
442 43
    public static function getCase($word, $case, $animateness = false)
443
    {
444 43
        $case = static::canonizeCase($case);
445 43
        $forms = static::getCases($word, $animateness);
446 43
        return $forms[$case];
447
    }
448
449
    /**
450
     * @param string $word
451
     * @param string $last
452
     * @return string
453
     */
454 104
    public static function getPrefixOfSecondDeclension($word, $last)
455
    {
456
        // слова с бегающей гласной в корне
457 104
        $runaway_vowels_list = static::getRunAwayVowelsList();
458 104
        if (isset($runaway_vowels_list[$word])) {
459 9
            $vowel_offset = $runaway_vowels_list[$word];
460 9
            $word = 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
        }
462
463 104
        if (in_array($last, ['о', 'е', 'ё', 'ь', 'й'], true)) {
464 47
            $prefix = S::slice($word, 0, -1);
465
        }
466
        // уменьшительные формы слов (котенок) и слова с суффиксом ок
467 60 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...
468 4
            $prefix = S::slice($word, 0, -2) . 'к';
469
        }
470
        // слова с суффиксом бец
471 56 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...
472 1
            $prefix = S::slice($word, 0, -3).'бц';
473
        } else {
474 55
            $prefix = $word;
475
        }
476 104
        return $prefix;
477
    }
478
479
    /**
480
     * @param string $word
481
     * @param string $last
482
     * @param string $prefix
483
     * @return string
484
     */
485 92
    public static function getPredCaseOf12Declensions($word, $last, $prefix)
486
    {
487 92
        if (in_array(S::slice($word, -2), ['ий', 'ие'], true)) {
488 6
            if ($last == 'ё') {
489
                return $prefix.'е';
490
            } else {
491 6
                return $prefix.'и';
492
            }
493
        } else {
494 86
            return $prefix.'е';
495
        }
496
    }
497
498
    /**
499
     * @return int[]|false[]
500
     */
501 104 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...
502
    {
503 104
        $runawayVowelsNormalized = [];
504 104
        foreach (NounDeclension::$runawayVowelsExceptions as $word) {
505 104
            $runawayVowelsNormalized[str_replace('*', '', $word)] = S::indexOf($word, '*') - 1;
506
        }
507 104
        return $runawayVowelsNormalized;
508
    }
509
}
510