Completed
Push — master ( 0d947c...778e4d )
by f
01:51
created

src/Russian/NounDeclension.php (1 issue)

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, 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
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...
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