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 = array( |
||
22 | 'бремя', |
||
23 | 'вымя', |
||
24 | 'темя', |
||
25 | 'пламя', |
||
26 | 'стремя', |
||
27 | 'пламя', |
||
28 | 'время', |
||
29 | 'знамя', |
||
30 | 'имя', |
||
31 | 'племя', |
||
32 | 'семя', |
||
33 | 'путь' => array('путь', 'пути', 'пути', 'путь', 'путем', 'пути'), |
||
34 | 'дитя' => array('дитя', 'дитяти', 'дитяти', 'дитя', 'дитятей', 'дитяти') |
||
35 | ); |
||
36 | |||
37 | protected static $masculineWithSoft = array( |
||
38 | 'олень', |
||
39 | 'конь', |
||
40 | 'ячмень', |
||
41 | 'путь', |
||
42 | 'зверь', |
||
43 | 'шкворень', |
||
44 | 'пельмень', |
||
45 | 'тюлень', |
||
46 | 'выхухоль', |
||
47 | 'табель', |
||
48 | 'рояль', |
||
49 | 'шампунь', |
||
50 | 'конь', |
||
51 | 'лось', |
||
52 | 'гвоздь', |
||
53 | 'медведь', |
||
54 | 'рубль', |
||
55 | 'дождь', |
||
56 | ); |
||
57 | |||
58 | protected static $masculineWithSoftAndRunAwayVowels = array( |
||
59 | 'день', |
||
60 | 'пень', |
||
61 | 'парень', |
||
62 | 'камень', |
||
63 | 'корень', |
||
64 | 'трутень', |
||
65 | ); |
||
66 | |||
67 | protected static $immutableWords = array( |
||
68 | 'евро', |
||
69 | 'пенни', |
||
70 | ); |
||
71 | |||
72 | public static function isMutable($word, $animateness = false) |
||
73 | { |
||
74 | $word = S::lower($word); |
||
75 | if (in_array(S::slice($word, -1), array('у', 'и', 'е', 'о', 'ю')) || in_array($word, self::$immutableWords)) { |
||
76 | return false; |
||
77 | } |
||
78 | return true; |
||
79 | } |
||
80 | |||
81 | public static function detectGender($word) |
||
82 | { |
||
83 | $word = S::lower($word); |
||
84 | $last = S::slice($word, -1); |
||
85 | // пытаемся угадать род объекта, хотя бы примерно, чтобы правильно склонять |
||
86 | if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у'])) |
||
87 | return self::NEUTER; |
||
88 | |||
89 | if (in_array($last, ['а', 'я']) || |
||
90 | ($last == 'ь' && !in_array($word, self::$masculineWithSoft) && !in_array($word, self::$masculineWithSoftAndRunAwayVowels))) |
||
91 | return self::FEMALE; |
||
92 | |||
93 | return self::MALE; |
||
94 | } |
||
95 | |||
96 | public static function getDeclension($word) |
||
97 | { |
||
98 | $word = S::lower($word); |
||
99 | $last = S::slice($word, -1); |
||
100 | if (isset(self::$abnormalExceptions[$word]) || in_array($word, self::$abnormalExceptions)) { |
||
101 | return 2; |
||
102 | } |
||
103 | |||
104 | if (in_array($last, ['а', 'я']) && S::slice($word, -2) != 'мя') { |
||
105 | return 1; |
||
106 | } elseif (self::isConsonant($last) || in_array($last, ['о', 'е', 'ё']) || ($last == 'ь' && self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1)) && (in_array($word, self::$masculineWithSoft)) || in_array($word, self::$masculineWithSoftAndRunAwayVowels))) { |
||
107 | return 2; |
||
108 | } else { |
||
109 | return 3; |
||
110 | } |
||
111 | } |
||
112 | |||
113 | public static function getCases($word, $animateness = false) |
||
114 | { |
||
115 | $word = S::lower($word); |
||
116 | |||
117 | // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное |
||
118 | View Code Duplication | if (in_array(S::slice($word, -2), array('ой', 'ий', 'ый', 'ая', 'ое', 'ее')) && $word != 'гений') { |
|
0 ignored issues
–
show
|
|||
119 | return self::declinateAdjective($word, $animateness); |
||
120 | } |
||
121 | |||
122 | // Субстантивное склонение (существительные) |
||
123 | if (in_array($word, self::$immutableWords)) { |
||
124 | return array( |
||
125 | self::IMENIT => $word, |
||
126 | self::RODIT => $word, |
||
127 | self::DAT => $word, |
||
128 | self::VINIT => $word, |
||
129 | self::TVORIT => $word, |
||
130 | self::PREDLOJ => $word, |
||
131 | ); |
||
132 | } elseif (isset(self::$abnormalExceptions[$word])) { |
||
133 | return array_combine(array(self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ), self::$abnormalExceptions[$word]); |
||
134 | } elseif (in_array($word, self::$abnormalExceptions)) { |
||
135 | $prefix = S::slice($word, 0, -1); |
||
136 | return array( |
||
137 | self::IMENIT => $word, |
||
138 | self::RODIT => $prefix.'ени', |
||
139 | self::DAT => $prefix.'ени', |
||
140 | self::VINIT => $word, |
||
141 | self::TVORIT => $prefix.'енем', |
||
142 | self::PREDLOJ => $prefix.'ени', |
||
143 | ); |
||
144 | } |
||
145 | |||
146 | switch (self::getDeclension($word)) { |
||
147 | case self::FIRST_DECLENSION: |
||
148 | return self::declinateFirstDeclension($word); |
||
149 | case self::SECOND_DECLENSION: |
||
150 | return self::declinateSecondDeclension($word, $animateness); |
||
151 | case self::THIRD_DECLENSION: |
||
152 | return self::declinateThirdDeclension($word); |
||
153 | } |
||
154 | } |
||
155 | |||
156 | public static function declinateFirstDeclension($word) |
||
157 | { |
||
158 | $word = S::lower($word); |
||
159 | $prefix = S::slice($word, 0, -1); |
||
160 | $last = S::slice($word, -1); |
||
161 | $soft_last = self::checkLastConsonantSoftness($word); |
||
162 | $forms = array( |
||
163 | Cases::IMENIT => $word, |
||
164 | ); |
||
165 | |||
166 | // RODIT |
||
167 | $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), array('г', 'к', 'х'))), $prefix.'и', $prefix.'ы'); |
||
168 | |||
169 | // DAT |
||
170 | $forms[Cases::DAT] = self::getPredCaseOf12Declensions($word, $last, $prefix); |
||
171 | |||
172 | // VINIT |
||
173 | $forms[Cases::VINIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ю', $prefix.'у'); |
||
174 | |||
175 | // TVORIT |
||
176 | if ($last == 'ь') { |
||
177 | $forms[Cases::TVORIT] = $prefix.'ой'; |
||
178 | View Code Duplication | } else { |
|
179 | $forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой'); |
||
180 | } |
||
181 | |||
182 | // if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last)) || self::checkLastConsonantSoftness($word)) |
||
183 | // $forms[Cases::TVORIT] = $prefix.'ей'; |
||
184 | // else |
||
185 | // $forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
||
186 | |||
187 | // PREDLOJ the same as DAT |
||
188 | $forms[Cases::PREDLOJ] = $forms[Cases::DAT]; |
||
189 | return $forms; |
||
190 | } |
||
191 | |||
192 | public static function declinateSecondDeclension($word, $animateness = false) |
||
193 | { |
||
194 | $word = S::lower($word); |
||
195 | $last = S::slice($word, -1); |
||
196 | $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && ((self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1))) || S::slice($word, -2, -1) == 'и')); |
||
197 | $prefix = self::getPrefixOfSecondDeclension($word, $last); |
||
198 | $forms = array( |
||
199 | Cases::IMENIT => $word, |
||
200 | ); |
||
201 | |||
202 | // RODIT |
||
203 | $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
||
204 | |||
205 | // DAT |
||
206 | $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у'); |
||
207 | |||
208 | // VINIT |
||
209 | if (in_array($last, ['о', 'е', 'ё'])) { |
||
210 | $forms[Cases::VINIT] = $word; |
||
211 | } else { |
||
212 | $forms[Cases::VINIT] = self::getVinitCaseByAnimateness($forms, $animateness); |
||
213 | } |
||
214 | |||
215 | // TVORIT |
||
216 | // if ($last == 'ь') |
||
217 | // $forms[Cases::TVORIT] = $prefix.'ом'; |
||
218 | // else if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last))) |
||
219 | // $forms[Cases::TVORIT] = $prefix.'ем'; |
||
220 | // else |
||
221 | // $forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
||
222 | if (self::isHissingConsonant($last) || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && self::isHissingConsonant(S::slice($word, -2, -1))) || $last == 'ц') { |
||
223 | $forms[Cases::TVORIT] = $prefix.'ем'; |
||
224 | View Code Duplication | } elseif (in_array($last, ['й'/*, 'ч', 'щ'*/]) || $soft_last) { |
|
225 | $forms[Cases::TVORIT] = $prefix.'ем'; |
||
226 | } else { |
||
227 | $forms[Cases::TVORIT] = $prefix.'ом'; |
||
228 | } |
||
229 | |||
230 | // PREDLOJ |
||
231 | $forms[Cases::PREDLOJ] = self::getPredCaseOf12Declensions($word, $last, $prefix); |
||
232 | $forms[Cases::PREDLOJ] = $forms[Cases::PREDLOJ]; |
||
233 | |||
234 | return $forms; |
||
235 | } |
||
236 | |||
237 | public static function declinateThirdDeclension($word) |
||
238 | { |
||
239 | $word = S::lower($word); |
||
240 | $prefix = S::slice($word, 0, -1); |
||
241 | return array( |
||
242 | Cases::IMENIT => $word, |
||
243 | Cases::RODIT => $prefix.'и', |
||
244 | Cases::DAT => $prefix.'и', |
||
245 | Cases::VINIT => $word, |
||
246 | Cases::TVORIT => $prefix.'ью', |
||
247 | Cases::PREDLOJ => $prefix.'и', |
||
248 | ); |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Rules are from http://rusgram.narod.ru/1216-1231.html |
||
253 | */ |
||
254 | public static function declinateAdjective($word, $animateness) |
||
255 | { |
||
256 | $prefix = S::slice($word, 0, -2); |
||
257 | |||
258 | switch (S::slice($word, -2)) { |
||
259 | // Male adjectives |
||
260 | case 'ой': |
||
261 | View Code Duplication | case 'ый': |
|
262 | return array( |
||
263 | Cases::IMENIT => $word, |
||
264 | Cases::RODIT => $prefix.'ого', |
||
265 | Cases::DAT => $prefix.'ому', |
||
266 | Cases::VINIT => $word, |
||
267 | Cases::TVORIT => $prefix.'ым', |
||
268 | Cases::PREDLOJ => $prefix.'ом', |
||
269 | ); |
||
270 | |||
271 | View Code Duplication | case 'ий': |
|
272 | return array( |
||
273 | Cases::IMENIT => $word, |
||
274 | Cases::RODIT => $prefix.'его', |
||
275 | Cases::DAT => $prefix.'ему', |
||
276 | Cases::VINIT => $prefix.'его', |
||
277 | Cases::TVORIT => $prefix.'им', |
||
278 | Cases::PREDLOJ => $prefix.'ем', |
||
279 | ); |
||
280 | |||
281 | // Neuter adjectives |
||
282 | case 'ое': |
||
283 | case 'ее': |
||
284 | $prefix = S::slice($word, 0, -1); |
||
285 | return array( |
||
286 | Cases::IMENIT => $word, |
||
287 | Cases::RODIT => $prefix.'го', |
||
288 | Cases::DAT => $prefix.'му', |
||
289 | Cases::VINIT => $word, |
||
290 | Cases::TVORIT => S::slice($word, 0, -2).(S::slice($word, -2, -1) == 'о' ? 'ы' : 'и').'м', |
||
291 | Cases::PREDLOJ => $prefix.'м', |
||
292 | ); |
||
293 | |||
294 | // Female adjectives |
||
295 | case 'ая': |
||
296 | $ending = self::isHissingConsonant(S::slice($prefix, -1)) ? 'ей' : 'ой'; |
||
297 | return array( |
||
298 | Cases::IMENIT => $word, |
||
299 | Cases::RODIT => $prefix.$ending, |
||
300 | Cases::DAT => $prefix.$ending, |
||
301 | Cases::VINIT => $prefix.'ую', |
||
302 | Cases::TVORIT => $prefix.$ending, |
||
303 | Cases::PREDLOJ => $prefix.$ending, |
||
304 | ); |
||
305 | } |
||
306 | } |
||
307 | |||
308 | public static function getCase($word, $case, $animateness = false) |
||
309 | { |
||
310 | $case = self::canonizeCase($case); |
||
311 | $forms = self::getCases($word, $animateness); |
||
312 | return $forms[$case]; |
||
313 | } |
||
314 | |||
315 | public static function getPrefixOfSecondDeclension($word, $last) |
||
316 | { |
||
317 | // слова с бегающей гласной в корне |
||
318 | if (in_array($word, self::$masculineWithSoftAndRunAwayVowels)) { |
||
319 | $prefix = S::slice($word, 0, -3).S::slice($word, -2, -1); |
||
320 | View Code Duplication | } elseif (in_array($last, ['о', 'е', 'ё', 'ь', 'й'])) { |
|
321 | $prefix = S::slice($word, 0, -1); |
||
322 | } |
||
323 | // уменьшительные формы слов (котенок) и слова с суффиксом ок |
||
324 | elseif (S::slice($word, -2) == 'ок' && S::length($word) > 3) { |
||
325 | $prefix = S::slice($word, 0, -2).'к'; |
||
326 | } else { |
||
327 | $prefix = $word; |
||
328 | } |
||
329 | return $prefix; |
||
330 | } |
||
331 | |||
332 | public static function getVinitCaseByAnimateness(array $forms, $animate) |
||
333 | { |
||
334 | if ($animate) { |
||
335 | return $forms[Cases::RODIT]; |
||
336 | } else { |
||
337 | return $forms[Cases::IMENIT]; |
||
338 | } |
||
339 | } |
||
340 | |||
341 | public static function getPredCaseOf12Declensions($word, $last, $prefix) |
||
342 | { |
||
343 | if (in_array(S::slice($word, -2), array('ий', 'ие'))) { |
||
344 | if ($last == 'ё') { |
||
345 | return $prefix.'е'; |
||
346 | } else { |
||
347 | return $prefix.'и'; |
||
348 | } |
||
349 | } else { |
||
350 | return $prefix.'е'; |
||
351 | } |
||
352 | } |
||
353 | } |
||
354 |
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.