NounPluralization::getCase()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 3
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
namespace morphos\Russian;
3
4
use morphos\S;
5
6
/**
7
 * Rules are from http://morpher.ru/Russian/Noun.aspx
8
 */
9
class NounPluralization extends \morphos\BasePluralization implements Cases
10
{
11
    use RussianLanguage, CasesHelper;
12
13
    const ONE = 1;
14
    const TWO_FOUR = 2;
15
    const FIVE_OTHER = 3;
16
17
    /**
18
     * @var string[][]
19
     * @phpstan-var array<string, string[]>
20
     */
21
    protected static $abnormalExceptions = [
22
        'человек' => ['люди', 'человек', 'людям', 'людей', 'людьми', 'людях'],
23
    ];
24
25
    /** @var string[] */
26
    protected static $neuterExceptions = [
27
        'поле',
28
        'море',
29
    ];
30
31
    /**
32
     * @var string[]
33
     * @phpstan-var array<string, string>
34
     */
35
    protected static $genitiveExceptions = [
36
        'письмо' => 'писем',
37
        'пятно' => 'пятен',
38
        'кресло' => 'кресел',
39
        'коромысло' => 'коромысел',
40
        'ядро' => 'ядер',
41
        'блюдце' => 'блюдец',
42
        'полотенце' => 'полотенец',
43
        'гривна' => 'гривен',
44
        'год' => 'лет',
45
    ];
46
47
    /**
48
     * Склонение существительного для сочетания с числом (кол-вом предметов).
49
     *
50
     * @param string|int $word        Название предмета
51
     * @param int|float|string $count Количество предметов
52
     * @param bool       $animateness Признак одушевленности
53
     * @param string     $case        Род существительного
54
     *
55
     * @return string
56
     * @throws \Exception
57
     */
58 62
    public static function pluralize($word, $count = 2, $animateness = false, $case = null)
59
    {
60
        // меняем местами аргументы, если они переданы в старом формате
61 62 View Code Duplication
        if (is_string($count) && is_numeric($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...
62 1
            list($count, $word) = [$word, $count];
63
        }
64
65 62
        if ($case !== null)
66 3
            $case = static::canonizeCase($case);
67
68
        // для адъективных существительных правила склонения проще:
69
        // только две формы
70 62 View Code Duplication
        if (static::isAdjectiveNoun($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...
71 3
            if (static::getNumeralForm($count) == static::ONE)
72 2
                return $word;
73
            else
74 3
                return NounPluralization::getCase($word,
75 3
                    $case !== null
76
                        ? $case
77 3
                        : static::RODIT, $animateness);
78
        }
79
80 60
        if ($case === null) {
81 57
            switch (static::getNumeralForm($count)) {
82 57
                case static::ONE:
83 29
                    return $word;
84 57
                case static::TWO_FOUR:
85 36
                    return NounDeclension::getCase($word, static::RODIT, $animateness);
86 44
                case static::FIVE_OTHER:
87 44
                    return NounPluralization::getCase($word, static::RODIT, $animateness);
88
            }
89
        }
90
91 3 View Code Duplication
        if (static::getNumeralForm($count) == static::ONE)
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...
92 3
            return NounDeclension::getCase($word, $case, $animateness);
93
        else
94 3
            return NounPluralization::getCase($word, $case, $animateness);
95
    }
96
97
    /**
98
     * @param int|float $count
99
     * @return int
100
     */
101 74
    public static function getNumeralForm($count)
102
    {
103 74
        if ($count > 100) {
104 48
            $count %= 100;
105
        }
106 74
        $ending = $count % 10;
107
108 74
        if (($count > 20 && $ending == 1) || $count == 1) {
109 38
            return static::ONE;
110 73
        } elseif (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) {
111 48
            return static::TWO_FOUR;
112
        } else {
113 56
            return static::FIVE_OTHER;
114
        }
115
    }
116
117
    /**
118
     * @param string $word
119
     * @param string $case
120
     * @param bool $animateness
121
     * @return string
122
     * @throws \Exception
123
     */
124 49
    public static function getCase($word, $case, $animateness = false)
125
    {
126 49
        $case = static::canonizeCase($case);
127 49
        $forms = static::getCases($word, $animateness);
128 49
        return $forms[$case];
129
    }
130
131
    /**
132
     * @param string $word
133
     * @param bool $animateness
134
     * @return string[]
135
     * @phpstan-return array<string, string>
136
     */
137 97
    public static function getCases($word, $animateness = false)
138
    {
139 97
        $word = S::lower($word);
140
141 97 View Code Duplication
        if (in_array($word, NounDeclension::$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...
142
            return [
143 1
                static::IMENIT => $word,
144 1
                static::RODIT => $word,
145 1
                static::DAT => $word,
146 1
                static::VINIT => $word,
147 1
                static::TVORIT => $word,
148 1
                static::PREDLOJ => $word,
149
            ];
150
        }
151
152 97 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...
153 1
            return array_combine(
154 1
                [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ],
155 1
                static::$abnormalExceptions[$word]);
156
        }
157
158
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий)
159
        // Пример: прохожий, существительное
160 96
        if (static::isAdjectiveNoun($word)) {
161 11
            return static::declinateAdjective($word, $animateness);
162
        }
163
164
        // Субстантивное склонение (существительные)
165 86
        return static::declinateSubstative($word, $animateness);
166
    }
167
168
    /**
169
     * Склонение обычных существительных.
170
     * @param string $word
171
     * @param bool $animateness
172
     * @return string[]
173
     * @phpstan-return array<string, string>
174
     */
175 86
    protected static function declinateSubstative($word, $animateness)
176
    {
177 86
        $prefix = S::slice($word, 0, -1);
178 86
        $last = S::slice($word, -1);
179
180 86
        if (($declension = NounDeclension::getDeclension($word)) == NounDeclension::SECOND_DECLENSION) {
181 56
            $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
182
                    && ((
183 21
                        static::isConsonant(S::slice($word, -2, -1)) && !static::isHissingConsonant(S::slice($word, -2, -1)))
184 56
                        || S::slice($word, -2, -1) == 'и'));
185 56
            $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last);
186 42
        } elseif ($declension == NounDeclension::FIRST_DECLENSION) {
187 37
            $soft_last = static::checkLastConsonantSoftness($word);
188
        } else {
189 5
            $soft_last = in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'дь'], true);
190
        }
191
192 86
        $forms = [];
193
194 86
        if (in_array($last, ['ч', 'г'], true)
195 84
            || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'рь', 'дь', 'ль'], true)
196 86
            || (static::isVowel($last) && in_array(S::slice($word, -2, -1), ['ч', 'к'], true))) { // before ч, чь, сь, ч+vowel, к+vowel
197 44
            $forms[Cases::IMENIT] = $prefix.'и';
198 48
        } elseif (in_array($last, ['н', 'ц', 'р', 'т'], true)) {
199 19
            $forms[Cases::IMENIT] = $prefix.'ы';
200 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...
201 33
            $forms[Cases::IMENIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
202
        }
203
204
        // RODIT
205 86
        if (isset(static::$genitiveExceptions[$word])) {
206 10
            $forms[Cases::RODIT] = static::$genitiveExceptions[$word];
207 78
        } elseif (in_array($last, ['о', 'е'], true)) {
208
            // exceptions
209 8
            if (in_array($word, static::$neuterExceptions, true)) {
210 3
                $forms[Cases::RODIT] = $prefix.'ей';
211 5 View Code Duplication
            } elseif (S::slice($word, -2, -1) == 'и') {
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...
212 2
                $forms[Cases::RODIT] = $prefix.'й';
213
            } else {
214 8
                $forms[Cases::RODIT] = $prefix;
215
            }
216 70
        } elseif (S::slice($word, -2) == 'ка' && S::slice($word, -3, -2) !== 'и') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка, аптека
217 13
            if (S::slice($word, -3, -2) == 'л') {
218 2
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок';
219 11
            } elseif (in_array(S::slice($word, -3, -2), ['й', 'е'], true)) {
220 9
                $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек';
221
            } else {
222 13
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек';
223
            }
224 63
        } elseif (in_array($last, ['а'], true)) { // обида, ябеда
225 23
            $forms[Cases::RODIT] = $prefix;
226 47 View Code Duplication
        } elseif (in_array($last, ['я'], 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...
227 1
            $forms[Cases::RODIT] = $prefix.'й';
228 46
        } elseif (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'дь'], true)) {
229 22
            $forms[Cases::RODIT] = $prefix.'ей';
230 27 View Code Duplication
        } elseif ($last == 'й' || S::slice($word, -2) == 'яц') { // месяц
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...
231 7
            $forms[Cases::RODIT] = $prefix.'ев';
232
        } else { // (static::isConsonant($last) && !RussianLanguage::isHissingConsonant($last))
233 20
            $forms[Cases::RODIT] = $prefix.'ов';
234
        }
235
236
        // DAT
237 86
        $forms[Cases::DAT] = static::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам');
238
239
        // VINIT
240 86
        $forms[Cases::VINIT] = NounDeclension::getVinitCaseByAnimateness($forms, $animateness);
241
242
        // TVORIT
243
        // my personal rule
244 86
        if ($last == 'ь' && $declension == NounDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'дь'], true)) {
245
            $forms[Cases::TVORIT] = $prefix.'ми';
246
        } else {
247 86
            $forms[Cases::TVORIT] = static::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами');
248
        }
249
250
        // PREDLOJ
251 86
        $forms[Cases::PREDLOJ] = static::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах');
252 86
        return $forms;
253
    }
254
255
    /**
256
     * Склонение существительных, образованных от прилагательных и причастий.
257
     * Rules are from http://rusgram.narod.ru/1216-1231.html
258
     * @param string $word
259
     * @param bool $animateness
260
     * @return string[]
261
     * @phpstan-return array<string, string>
262
     */
263 11
    protected static function declinateAdjective($word, $animateness)
264
    {
265 11
        $prefix = S::slice($word, 0, -2);
266 11
        $vowel = static::isHissingConsonant(S::slice($prefix, -1)) ? 'и' : 'ы';
267
        return [
268 11
            Cases::IMENIT => $prefix.$vowel.'е',
269 11
            Cases::RODIT => $prefix.$vowel.'х',
270 11
            Cases::DAT => $prefix.$vowel.'м',
271 11
            Cases::VINIT => $prefix.$vowel.($animateness ? 'х' : 'е'),
272 11
            Cases::TVORIT => $prefix.$vowel.'ми',
273 11
            Cases::PREDLOJ => $prefix.$vowel.'х',
274
        ];
275
    }
276
}
277