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

src/Russian/NounDeclension.php (1 issue)

parameters are used.

Unused Code Minor

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