Completed
Push — master ( 38f7c0...210f74 )
by f
01:42
created

src/Russian/NounDeclension.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

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, self::$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
		if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у'], true))
117 2
			return self::NEUTER;
118
119 6
		if (in_array($last, ['а', 'я'], true) ||
120 6
			($last == 'ь' && !in_array($word, self::$masculineWithSoft, true) && !in_array($word, self::$masculineWithSoftAndRunAwayVowels, true)))
121 3
			return self::FEMALE;
122
123 3
		return self::MALE;
124
    }
125
126
    /**
127
     * Определение склонения (по школьной программе) существительного.
128
     * @param $word
129
     * @param bool $animateness
130
     * @return int
131
     */
132 174
    public static function getDeclension($word, $animateness = false)
0 ignored issues
show
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...
133
    {
134 174
        $word = S::lower($word);
135 174
        $last = S::slice($word, -1);
136 174
        if (isset(self::$abnormalExceptions[$word]) || in_array($word, self::$abnormalExceptions, true)) {
137 3
            return 2;
138
        }
139
140 171
        if (in_array($last, ['а', 'я'], true) && S::slice($word, -2) != 'мя') {
141 56
            return 1;
142 124
        } elseif (self::isConsonant($last) || in_array($last, ['о', 'е', 'ё'], true)
143 32
            || ($last == 'ь' && self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1))
144 124
                && (in_array($word, self::$masculineWithSoft, true)) || in_array($word, self::$masculineWithSoftAndRunAwayVowels, true))) {
145 115
            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 98
    public static function getCases($word, $animateness = false)
158
    {
159 98
        $word = S::lower($word);
160
161
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
162 98
        if (self::isAdjectiveNoun($word)) {
163 8
            return self::declinateAdjective($word, $animateness);
164
        }
165
166
        // Субстантивное склонение (существительные)
167 90
        if (in_array($word, self::$immutableWords, true)) {
168
            return [
169 3
                self::IMENIT => $word,
170 3
                self::RODIT => $word,
171 3
                self::DAT => $word,
172 3
                self::VINIT => $word,
173 3
                self::TVORIT => $word,
174 3
                self::PREDLOJ => $word,
175
            ];
176 87
        } elseif (isset(self::$abnormalExceptions[$word])) {
177 2
            return array_combine([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], self::$abnormalExceptions[$word]);
178 85
        } elseif (in_array($word, self::$abnormalExceptions, true)) {
179 1
            $prefix = S::slice($word, 0, -1);
180
            return [
181 1
                self::IMENIT => $word,
182 1
                self::RODIT => $prefix.'ени',
183 1
                self::DAT => $prefix.'ени',
184 1
                self::VINIT => $word,
185 1
                self::TVORIT => $prefix.'енем',
186 1
                self::PREDLOJ => $prefix.'ени',
187
            ];
188
        }
189
190 84
        switch (self::getDeclension($word)) {
191 84
            case self::FIRST_DECLENSION:
192 25
                return self::declinateFirstDeclension($word);
193 61
            case self::SECOND_DECLENSION:
194 57
                return self::declinateSecondDeclension($word, $animateness);
195 4
            case self::THIRD_DECLENSION:
196 4
                return self::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 = self::checkLastConsonantSoftness($word);
211
        $forms =  [
212 25
            Cases::IMENIT => $word,
213
        ];
214
215
        // RODIT
216 25
        $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), ['г', 'к', 'х'], true)), $prefix.'и', $prefix.'ы');
217
218
        // DAT
219 25
        $forms[Cases::DAT] = self::getPredCaseOf12Declensions($word, $last, $prefix);
220
221
        // VINIT
222 25
        $forms[Cases::VINIT] = self::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] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой');
229
        }
230
231
        // 	if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last)) || self::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 57
    public static function declinateSecondDeclension($word, $animateness = false)
248
    {
249 57
        $word = S::lower($word);
250 57
        $last = S::slice($word, -1);
251 57
        $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
252
            && ((
253 21
                self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1)))
254 57
                    || S::slice($word, -2, -1) == 'и'));
255 57
        $prefix = self::getPrefixOfSecondDeclension($word, $last);
256
        $forms =  [
257 57
            Cases::IMENIT => $word,
258
        ];
259
260
        // RODIT
261 57
        $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
262
263
        // DAT
264 57
        $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
265
266
        // VINIT
267 57
        if (in_array($last, ['о', 'е', 'ё'], true)) {
268 12
            $forms[Cases::VINIT] = $word;
269
        } else {
270 45
            $forms[Cases::VINIT] = self::getVinitCaseByAnimateness($forms, $animateness);
271
        }
272
273
        // TVORIT
274
        // if ($last == 'ь')
275
        // 	$forms[Cases::TVORIT] = $prefix.'ом';
276
        // else if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last)))
277
        // 	$forms[Cases::TVORIT] = $prefix.'ем';
278
        // else
279
        // 	$forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
280 57
        if (self::isHissingConsonant($last) || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) && self::isHissingConsonant(S::slice($word, -2, -1))) || $last == 'ц') {
281 5
            $forms[Cases::TVORIT] = $prefix.'ем';
282 52 View Code Duplication
        } elseif (in_array($last, ['й'/*, 'ч', 'щ'*/], true) || $soft_last) {
283 22
            $forms[Cases::TVORIT] = $prefix.'ем';
284
        } else {
285 30
            $forms[Cases::TVORIT] = $prefix.'ом';
286
        }
287
288
        // PREDLOJ
289 57
        $forms[Cases::PREDLOJ] = self::getPredCaseOf12Declensions($word, $last, $prefix);
290
291 57
        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 = self::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 37
    public static function getCase($word, $case, $animateness = false)
383
    {
384 37
        $case = self::canonizeCase($case);
385 37
        $forms = self::getCases($word, $animateness);
386 37
        return $forms[$case];
387
    }
388
389
    /**
390
     * @param $word
391
     * @param $last
392
     * @return bool
393
     */
394 85
    public static function getPrefixOfSecondDeclension($word, $last)
395
    {
396
        // слова с бегающей гласной в корне
397 85
        if (in_array($word, self::$masculineWithSoftAndRunAwayVowels, true)) {
398 7
            $prefix = S::slice($word, 0, -3).S::slice($word, -2, -1);
399 80
        } elseif (in_array($last, ['о', 'е', 'ё', 'ь', 'й'], true)) {
400 32
            $prefix = S::slice($word, 0, -1);
401
        }
402
        // уменьшительные формы слов (котенок) и слова с суффиксом ок
403 48
        elseif (S::slice($word, -2) == 'ок' && S::length($word) > 3) {
404 4
            $prefix = S::slice($word, 0, -2).'к';
405
        } else {
406 44
            $prefix = $word;
407
        }
408 85
        return $prefix;
409
    }
410
411
    /**
412
     * @param array $forms
413
     * @param $animate
414
     * @return mixed
415
     */
416 104
    public static function getVinitCaseByAnimateness(array $forms, $animate)
417
    {
418 104
        if ($animate) {
419 9
            return $forms[Cases::RODIT];
420
        } else {
421 95
            return $forms[Cases::IMENIT];
422
        }
423
    }
424
425
    /**
426
     * @param $word
427
     * @param $last
428
     * @param $prefix
429
     * @return string
430
     */
431 80
    public static function getPredCaseOf12Declensions($word, $last, $prefix)
432
    {
433 80
        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 75
            return $prefix.'е';
441
        }
442
    }
443
}
444