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\Gender; |
||
5 | use morphos\S; |
||
6 | |||
7 | /** |
||
8 | * Rules are from http://morpher.ru/Russian/Noun.aspx |
||
9 | */ |
||
10 | class NounDeclension extends \morphos\BaseInflection implements Cases, Gender |
||
11 | { |
||
12 | use RussianLanguage, CasesHelper; |
||
13 | |||
14 | const FIRST_DECLENSION = 1; |
||
15 | const SECOND_DECLENSION = 2; |
||
16 | const THIRD_DECLENSION = 3; |
||
17 | |||
18 | /** |
||
19 | * These words has 2 declension type. |
||
20 | */ |
||
21 | protected static $abnormalExceptions = [ |
||
22 | 'бремя', |
||
23 | 'вымя', |
||
24 | 'темя', |
||
25 | 'пламя', |
||
26 | 'стремя', |
||
27 | 'пламя', |
||
28 | 'время', |
||
29 | 'знамя', |
||
30 | 'имя', |
||
31 | 'племя', |
||
32 | 'семя', |
||
33 | 'путь' => ['путь', 'пути', 'пути', 'путь', 'путем', 'пути'], |
||
34 | 'дитя' => ['дитя', 'дитяти', 'дитяти', 'дитя', 'дитятей', 'дитяти'], |
||
35 | ]; |
||
36 | |||
37 | protected static $masculineWithSoft = [ |
||
38 | 'ячмень', |
||
39 | 'путь', |
||
40 | 'шкворень', |
||
41 | 'пельмень', |
||
42 | 'табель', |
||
43 | 'рояль', |
||
44 | 'шампунь', |
||
45 | 'гвоздь', |
||
46 | 'рубль', |
||
47 | 'дождь', |
||
48 | 'зверь', |
||
49 | 'юань', |
||
50 | 'олень', |
||
51 | 'конь', |
||
52 | 'конь', |
||
53 | 'лось', |
||
54 | 'тюлень', |
||
55 | 'выхухоль', |
||
56 | 'медведь', |
||
57 | 'председатель', |
||
58 | 'руководитель', |
||
59 | 'заместитель', |
||
60 | ]; |
||
61 | |||
62 | protected static $masculineWithSoftAndRunAwayVowels = [ |
||
63 | 'день', |
||
64 | 'пень', |
||
65 | 'парень', |
||
66 | 'камень', |
||
67 | 'корень', |
||
68 | 'трутень', |
||
69 | ]; |
||
70 | |||
71 | protected static $immutableWords = [ |
||
72 | // валюты |
||
73 | 'евро', 'пенни', 'песо', 'сентаво', |
||
74 | |||
75 | // на а |
||
76 | 'боа', 'бра', 'фейхоа', 'амплуа', 'буржуа', |
||
77 | // на о |
||
78 | 'манго', 'какао', 'кино', 'трюмо', 'пальто', 'бюро', 'танго', 'вето', 'бунгало', 'сабо', 'авокадо', 'депо', |
||
79 | // на у |
||
80 | 'зебу', 'кенгуру', 'рагу', 'какаду', 'шоу', |
||
81 | // на е |
||
82 | 'шимпанзе', 'конферансье', 'атташе', 'колье', 'резюме', 'пенсне', 'кашне', 'протеже', 'коммюнике', 'драже', 'суфле', 'пюре', 'купе', 'фойе', 'шоссе', |
||
83 | // на и |
||
84 | 'такси', 'жалюзи', 'шасси', 'алиби', 'киви', 'иваси', 'регби', 'конфетти', 'колибри', 'жюри', 'пенальти', 'рефери', 'кольраби', |
||
85 | // на э |
||
86 | 'каноэ', 'алоэ', |
||
87 | // на ю |
||
88 | 'меню', 'парвеню', 'авеню', 'дежавю', 'инженю', 'барбекю', 'интервью', |
||
89 | ]; |
||
90 | |||
91 | /** |
||
92 | * Проверка, изменяемое ли слово. |
||
93 | * @param string $word Слово для проверки |
||
94 | * @param bool $animateness Признак одушевленности |
||
95 | * @return bool |
||
96 | */ |
||
97 | 43 | public static function isMutable($word, $animateness = false) |
|
98 | { |
||
99 | 43 | $word = S::lower($word); |
|
100 | 43 | if (in_array(S::slice($word, -1), ['у', 'и', 'е', 'о', 'ю'], true) || in_array($word, static::$immutableWords, true)) { |
|
101 | 43 | return false; |
|
102 | } |
||
103 | return true; |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * Определение рода существительного. |
||
108 | * @param string $word |
||
109 | * @return string |
||
110 | */ |
||
111 | 8 | public static function detectGender($word) |
|
112 | { |
||
113 | 8 | $word = S::lower($word); |
|
114 | 8 | $last = S::slice($word, -1); |
|
115 | // пытаемся угадать род объекта, хотя бы примерно, чтобы правильно склонять |
||
116 | 8 | View Code Duplication | if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у'], true)) |
0 ignored issues
–
show
|
|||
117 | 2 | return static::NEUTER; |
|
118 | |||
119 | 6 | if (in_array($last, ['а', 'я'], true) || |
|
120 | 6 | ($last == 'ь' && !in_array($word, static::$masculineWithSoft, true) && !in_array($word, static::$masculineWithSoftAndRunAwayVowels, true))) |
|
121 | 3 | return static::FEMALE; |
|
122 | |||
123 | 3 | return static::MALE; |
|
124 | } |
||
125 | |||
126 | /** |
||
127 | * Определение склонения (по школьной программе) существительного. |
||
128 | * @param $word |
||
129 | * @param bool $animateness |
||
130 | * @return int |
||
131 | */ |
||
132 | 178 | public static function getDeclension($word, $animateness = false) |
|
133 | { |
||
134 | 178 | $word = S::lower($word); |
|
135 | 178 | $last = S::slice($word, -1); |
|
136 | 178 | if (isset(static::$abnormalExceptions[$word]) || in_array($word, static::$abnormalExceptions, true)) { |
|
137 | 3 | return 2; |
|
138 | } |
||
139 | |||
140 | 175 | if (in_array($last, ['а', 'я'], true) && S::slice($word, -2) != 'мя') { |
|
141 | 56 | return 1; |
|
142 | 128 | } elseif (static::isConsonant($last) || in_array($last, ['о', 'е', 'ё'], true) |
|
143 | 32 | || ($last == 'ь' && static::isConsonant(S::slice($word, -2, -1)) && !static::isHissingConsonant(S::slice($word, -2, -1)) |
|
144 | 128 | && (in_array($word, static::$masculineWithSoft, true)) || in_array($word, static::$masculineWithSoftAndRunAwayVowels, true))) { |
|
145 | 119 | return 2; |
|
146 | } else { |
||
147 | 9 | return 3; |
|
148 | } |
||
149 | } |
||
150 | |||
151 | /** |
||
152 | * Получение слова во всех 6 падежах. |
||
153 | * @param string $word |
||
154 | * @param bool $animateness Признак одушевлённости |
||
155 | * @return array |
||
156 | */ |
||
157 | 100 | public static function getCases($word, $animateness = false) |
|
158 | { |
||
159 | 100 | $word = S::lower($word); |
|
160 | |||
161 | // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное |
||
162 | 100 | if (static::isAdjectiveNoun($word)) { |
|
163 | 8 | return static::declinateAdjective($word, $animateness); |
|
164 | } |
||
165 | |||
166 | // Субстантивное склонение (существительные) |
||
167 | 92 | if (in_array($word, static::$immutableWords, true)) { |
|
168 | return [ |
||
169 | 3 | static::IMENIT => $word, |
|
170 | 3 | static::RODIT => $word, |
|
171 | 3 | static::DAT => $word, |
|
172 | 3 | static::VINIT => $word, |
|
173 | 3 | static::TVORIT => $word, |
|
174 | 3 | static::PREDLOJ => $word, |
|
175 | ]; |
||
176 | 89 | } elseif (isset(static::$abnormalExceptions[$word])) { |
|
177 | 2 | return array_combine([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], static::$abnormalExceptions[$word]); |
|
178 | 87 | } elseif (in_array($word, static::$abnormalExceptions, true)) { |
|
179 | 1 | $prefix = S::slice($word, 0, -1); |
|
180 | return [ |
||
181 | 1 | static::IMENIT => $word, |
|
182 | 1 | static::RODIT => $prefix.'ени', |
|
183 | 1 | static::DAT => $prefix.'ени', |
|
184 | 1 | static::VINIT => $word, |
|
185 | 1 | static::TVORIT => $prefix.'енем', |
|
186 | 1 | static::PREDLOJ => $prefix.'ени', |
|
187 | ]; |
||
188 | } |
||
189 | |||
190 | 86 | switch (static::getDeclension($word)) { |
|
191 | 86 | case static::FIRST_DECLENSION: |
|
192 | 25 | return static::declinateFirstDeclension($word); |
|
193 | 63 | case static::SECOND_DECLENSION: |
|
194 | 59 | return static::declinateSecondDeclension($word, $animateness); |
|
195 | 4 | case static::THIRD_DECLENSION: |
|
196 | 4 | return static::declinateThirdDeclension($word); |
|
197 | } |
||
198 | } |
||
199 | |||
200 | /** |
||
201 | * Получение всех форм слова первого склонения. |
||
202 | * @param $word |
||
203 | * @return array |
||
204 | */ |
||
205 | 25 | public static function declinateFirstDeclension($word) |
|
206 | { |
||
207 | 25 | $word = S::lower($word); |
|
208 | 25 | $prefix = S::slice($word, 0, -1); |
|
209 | 25 | $last = S::slice($word, -1); |
|
210 | 25 | $soft_last = static::checkLastConsonantSoftness($word); |
|
211 | $forms = [ |
||
212 | 25 | Cases::IMENIT => $word, |
|
213 | ]; |
||
214 | |||
215 | // RODIT |
||
216 | 25 | $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), ['г', 'к', 'х'], true)), $prefix.'и', $prefix.'ы'); |
|
217 | |||
218 | // DAT |
||
219 | 25 | $forms[Cases::DAT] = static::getPredCaseOf12Declensions($word, $last, $prefix); |
|
220 | |||
221 | // VINIT |
||
222 | 25 | $forms[Cases::VINIT] = static::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ю', $prefix.'у'); |
|
223 | |||
224 | // TVORIT |
||
225 | 25 | if ($last == 'ь') { |
|
226 | $forms[Cases::TVORIT] = $prefix.'ой'; |
||
227 | View Code Duplication | } else { |
|
228 | 25 | $forms[Cases::TVORIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой'); |
|
229 | } |
||
230 | |||
231 | // if ($last == 'й' || (static::isConsonant($last) && !static::isHissingConsonant($last)) || static::checkLastConsonantSoftness($word)) |
||
232 | // $forms[Cases::TVORIT] = $prefix.'ей'; |
||
233 | // else |
||
234 | // $forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
||
235 | |||
236 | // PREDLOJ the same as DAT |
||
237 | 25 | $forms[Cases::PREDLOJ] = $forms[Cases::DAT]; |
|
238 | 25 | return $forms; |
|
239 | } |
||
240 | |||
241 | /** |
||
242 | * Получение всех форм слова второго склонения. |
||
243 | * @param $word |
||
244 | * @param bool $animateness |
||
245 | * @return array |
||
246 | */ |
||
247 | 59 | public static function declinateSecondDeclension($word, $animateness = false) |
|
248 | { |
||
249 | 59 | $word = S::lower($word); |
|
250 | 59 | $last = S::slice($word, -1); |
|
251 | 59 | $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) |
|
252 | && (( |
||
253 | 21 | static::isConsonant(S::slice($word, -2, -1)) && !static::isHissingConsonant(S::slice($word, -2, -1))) |
|
254 | 59 | || S::slice($word, -2, -1) == 'и')); |
|
255 | 59 | $prefix = static::getPrefixOfSecondDeclension($word, $last); |
|
256 | $forms = [ |
||
257 | 59 | Cases::IMENIT => $word, |
|
258 | ]; |
||
259 | |||
260 | // RODIT |
||
261 | 59 | $forms[Cases::RODIT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
|
262 | |||
263 | // DAT |
||
264 | 59 | $forms[Cases::DAT] = static::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у'); |
|
265 | |||
266 | // VINIT |
||
267 | 59 | if (in_array($last, ['о', 'е', 'ё'], true)) { |
|
268 | 12 | $forms[Cases::VINIT] = $word; |
|
269 | } else { |
||
270 | 47 | $forms[Cases::VINIT] = static::getVinitCaseByAnimateness($forms, $animateness); |
|
271 | } |
||
272 | |||
273 | // TVORIT |
||
274 | // if ($last == 'ь') |
||
275 | // $forms[Cases::TVORIT] = $prefix.'ом'; |
||
276 | // else if ($last == 'й' || (static::isConsonant($last) && !static::isHissingConsonant($last))) |
||
277 | // $forms[Cases::TVORIT] = $prefix.'ем'; |
||
278 | // else |
||
279 | // $forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
||
280 | 59 | if (static::isHissingConsonant($last) || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) && static::isHissingConsonant(S::slice($word, -2, -1))) || $last == 'ц') { |
|
281 | 5 | $forms[Cases::TVORIT] = $prefix.'ем'; |
|
282 | 54 | View Code Duplication | } elseif (in_array($last, ['й'/*, 'ч', 'щ'*/], true) || $soft_last) { |
283 | 22 | $forms[Cases::TVORIT] = $prefix.'ем'; |
|
284 | } else { |
||
285 | 32 | $forms[Cases::TVORIT] = $prefix.'ом'; |
|
286 | } |
||
287 | |||
288 | // PREDLOJ |
||
289 | 59 | $forms[Cases::PREDLOJ] = static::getPredCaseOf12Declensions($word, $last, $prefix); |
|
290 | |||
291 | 59 | return $forms; |
|
292 | } |
||
293 | |||
294 | /** |
||
295 | * Получение всех форм слова третьего склонения. |
||
296 | * @param $word |
||
297 | * @return array |
||
298 | */ |
||
299 | 4 | public static function declinateThirdDeclension($word) |
|
300 | { |
||
301 | 4 | $word = S::lower($word); |
|
302 | 4 | $prefix = S::slice($word, 0, -1); |
|
303 | return [ |
||
304 | 4 | Cases::IMENIT => $word, |
|
305 | 4 | Cases::RODIT => $prefix.'и', |
|
306 | 4 | Cases::DAT => $prefix.'и', |
|
307 | 4 | Cases::VINIT => $word, |
|
308 | 4 | Cases::TVORIT => $prefix.'ью', |
|
309 | 4 | Cases::PREDLOJ => $prefix.'и', |
|
310 | ]; |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Склонение существительных, образованных от прилагательных и причастий. |
||
315 | * Rules are from http://rusgram.narod.ru/1216-1231.html |
||
316 | * @param $word |
||
317 | * @param $animateness |
||
318 | * @return array |
||
319 | */ |
||
320 | 8 | public static function declinateAdjective($word, $animateness) |
|
321 | { |
||
322 | 8 | $prefix = S::slice($word, 0, -2); |
|
323 | |||
324 | 8 | switch (S::slice($word, -2)) { |
|
325 | // Male adjectives |
||
326 | 8 | case 'ой': |
|
327 | 7 | View Code Duplication | case 'ый': |
328 | return [ |
||
329 | 2 | Cases::IMENIT => $word, |
|
330 | 2 | Cases::RODIT => $prefix.'ого', |
|
331 | 2 | Cases::DAT => $prefix.'ому', |
|
332 | 2 | Cases::VINIT => $word, |
|
333 | 2 | Cases::TVORIT => $prefix.'ым', |
|
334 | 2 | Cases::PREDLOJ => $prefix.'ом', |
|
335 | ]; |
||
336 | |||
337 | 6 | View Code Duplication | case 'ий': |
338 | return [ |
||
339 | 1 | Cases::IMENIT => $word, |
|
340 | 1 | Cases::RODIT => $prefix.'его', |
|
341 | 1 | Cases::DAT => $prefix.'ему', |
|
342 | 1 | Cases::VINIT => $prefix.'его', |
|
343 | 1 | Cases::TVORIT => $prefix.'им', |
|
344 | 1 | Cases::PREDLOJ => $prefix.'ем', |
|
345 | ]; |
||
346 | |||
347 | // Neuter adjectives |
||
348 | 5 | case 'ое': |
|
349 | 4 | View Code Duplication | case 'ее': |
350 | 2 | $prefix = S::slice($word, 0, -1); |
|
351 | return [ |
||
352 | 2 | Cases::IMENIT => $word, |
|
353 | 2 | Cases::RODIT => $prefix.'го', |
|
354 | 2 | Cases::DAT => $prefix.'му', |
|
355 | 2 | Cases::VINIT => $word, |
|
356 | 2 | Cases::TVORIT => S::slice($word, 0, -2).(S::slice($word, -2, -1) == 'о' ? 'ы' : 'и').'м', |
|
357 | 2 | Cases::PREDLOJ => $prefix.'м', |
|
358 | ]; |
||
359 | |||
360 | // Female adjectives |
||
361 | 3 | case 'ая': |
|
362 | 3 | $ending = static::isHissingConsonant(S::slice($prefix, -1)) ? 'ей' : 'ой'; |
|
363 | return [ |
||
364 | 3 | Cases::IMENIT => $word, |
|
365 | 3 | Cases::RODIT => $prefix.$ending, |
|
366 | 3 | Cases::DAT => $prefix.$ending, |
|
367 | 3 | Cases::VINIT => $prefix.'ую', |
|
368 | 3 | Cases::TVORIT => $prefix.$ending, |
|
369 | 3 | Cases::PREDLOJ => $prefix.$ending, |
|
370 | ]; |
||
371 | } |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Получение одной формы слова (падежа). |
||
376 | * @param string $word Слово |
||
377 | * @param integer $case Падеж |
||
378 | * @param bool $animateness Признак одушевленности |
||
379 | * @return string |
||
380 | * @throws \Exception |
||
381 | */ |
||
382 | 39 | public static function getCase($word, $case, $animateness = false) |
|
383 | { |
||
384 | 39 | $case = static::canonizeCase($case); |
|
385 | 39 | $forms = static::getCases($word, $animateness); |
|
386 | 39 | return $forms[$case]; |
|
387 | } |
||
388 | |||
389 | /** |
||
390 | * @param $word |
||
391 | * @param $last |
||
392 | * @return bool |
||
393 | */ |
||
394 | 89 | public static function getPrefixOfSecondDeclension($word, $last) |
|
395 | { |
||
396 | // слова с бегающей гласной в корне |
||
397 | 89 | if (in_array($word, static::$masculineWithSoftAndRunAwayVowels, true)) { |
|
398 | 7 | $prefix = S::slice($word, 0, -3).S::slice($word, -2, -1); |
|
399 | 84 | } elseif (in_array($last, ['о', 'е', 'ё', 'ь', 'й'], true)) { |
|
400 | 32 | $prefix = S::slice($word, 0, -1); |
|
401 | } |
||
402 | // уменьшительные формы слов (котенок) и слова с суффиксом ок |
||
403 | 52 | elseif (S::slice($word, -2) == 'ок' && S::length($word) > 3) { |
|
404 | 4 | $prefix = S::slice($word, 0, -2).'к'; |
|
405 | } else { |
||
406 | 48 | $prefix = $word; |
|
407 | } |
||
408 | 89 | return $prefix; |
|
409 | } |
||
410 | |||
411 | /** |
||
412 | * @param array $forms |
||
413 | * @param $animate |
||
414 | * @return mixed |
||
415 | */ |
||
416 | 108 | public static function getVinitCaseByAnimateness(array $forms, $animate) |
|
417 | { |
||
418 | 108 | if ($animate) { |
|
419 | 9 | return $forms[Cases::RODIT]; |
|
420 | } else { |
||
421 | 99 | return $forms[Cases::IMENIT]; |
|
422 | } |
||
423 | } |
||
424 | |||
425 | /** |
||
426 | * @param $word |
||
427 | * @param $last |
||
428 | * @param $prefix |
||
429 | * @return string |
||
430 | */ |
||
431 | 82 | public static function getPredCaseOf12Declensions($word, $last, $prefix) |
|
432 | { |
||
433 | 82 | if (in_array(S::slice($word, -2), ['ий', 'ие'], true)) { |
|
434 | 5 | if ($last == 'ё') { |
|
435 | return $prefix.'е'; |
||
436 | } else { |
||
437 | 5 | return $prefix.'и'; |
|
438 | } |
||
439 | } else { |
||
440 | 77 | return $prefix.'е'; |
|
441 | } |
||
442 | } |
||
443 | } |
||
444 |
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.