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