Completed
Push — master ( 622740...e78b73 )
by f
01:58
created

src/Russian/NounDeclension.php (5 issues)

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