Completed
Push — master ( 13fe0a...7029b0 )
by f
02:11
created

NounDeclension::getCases()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 42
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 30
nc 8
nop 2
dl 0
loc 42
rs 5.3846
c 0
b 0
f 0
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
    /**
73
     * Проверка, изменяемое ли слово.
74
     * @param $word
75
     * @param bool $animateness Признак одушевленности
76
     * @return bool
77
     */
78
    public static function isMutable($word, $animateness = false)
0 ignored issues
show
Unused Code introduced by
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...
79
    {
80
        $word = S::lower($word);
81
        if (in_array(S::slice($word, -1), array('у', 'и', 'е', 'о', 'ю')) || in_array($word, self::$immutableWords)) {
82
            return false;
83
        }
84
        return true;
85
    }
86
87
    /**
88
     * Определение рода существительного.
89
     * @param $word
90
     * @return string
91
     */
92
    public static function detectGender($word)
93
    {
94
    	$word = S::lower($word);
95
    	$last = S::slice($word, -1);
96
		// пытаемся угадать род объекта, хотя бы примерно, чтобы правильно склонять
97
		if (S::slice($word, -2) == 'мя' || in_array($last, ['о', 'е', 'и', 'у']))
98
			return self::NEUTER;
99
100
		if (in_array($last, ['а', 'я']) ||
101
			($last == 'ь' && !in_array($word, self::$masculineWithSoft) && !in_array($word, self::$masculineWithSoftAndRunAwayVowels)))
102
			return self::FEMALE;
103
104
		return self::MALE;
105
    }
106
107
    /**
108
     * Определение склонения (по школьной программе) существительного.
109
     * @param $word
110
     * @return int
111
     */
112
    public static function getDeclension($word)
113
    {
114
        $word = S::lower($word);
115
        $last = S::slice($word, -1);
116
        if (isset(self::$abnormalExceptions[$word]) || in_array($word, self::$abnormalExceptions)) {
117
            return 2;
118
        }
119
120
        if (in_array($last, ['а', 'я']) && S::slice($word, -2) != 'мя') {
121
            return 1;
122
        } 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))) {
123
            return 2;
124
        } else {
125
            return 3;
126
        }
127
    }
128
129
    /**
130
     * Получение слова во всех 6 падежах.
131
     * @param $word
132
     * @param bool $animateness
133
     * @return array
134
     */
135
    public static function getCases($word, $animateness = false)
136
    {
137
        $word = S::lower($word);
138
139
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
140
        if (self::isAdjectiveNoun($word)) {
0 ignored issues
show
Security Bug introduced by
It seems like $word defined by \morphos\S::lower($word) on line 137 can also be of type false; however, morphos\Russian\RussianLanguage::isAdjectiveNoun() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
141
            return self::declinateAdjective($word, $animateness);
142
        }
143
144
        // Субстантивное склонение (существительные)
145
        if (in_array($word, self::$immutableWords)) {
146
            return array(
147
                self::IMENIT => $word,
148
                self::RODIT => $word,
149
                self::DAT => $word,
150
                self::VINIT => $word,
151
                self::TVORIT => $word,
152
                self::PREDLOJ => $word,
153
            );
154
        } elseif (isset(self::$abnormalExceptions[$word])) {
155
            return array_combine(array(self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ), self::$abnormalExceptions[$word]);
156
        } elseif (in_array($word, self::$abnormalExceptions)) {
157
            $prefix = S::slice($word, 0, -1);
158
            return array(
159
                self::IMENIT => $word,
160
                self::RODIT => $prefix.'ени',
161
                self::DAT => $prefix.'ени',
162
                self::VINIT => $word,
163
                self::TVORIT => $prefix.'енем',
164
                self::PREDLOJ => $prefix.'ени',
165
            );
166
        }
167
168
        switch (self::getDeclension($word)) {
169
            case self::FIRST_DECLENSION:
170
                return self::declinateFirstDeclension($word);
171
            case self::SECOND_DECLENSION:
172
                return self::declinateSecondDeclension($word, $animateness);
173
            case self::THIRD_DECLENSION:
174
                return self::declinateThirdDeclension($word);
175
        }
176
    }
177
178
    /**
179
     * Получение всех форм слова первого склонения.
180
     * @param $word
181
     * @return array
182
     */
183
    public static function declinateFirstDeclension($word)
184
    {
185
        $word = S::lower($word);
186
        $prefix = S::slice($word, 0, -1);
187
        $last = S::slice($word, -1);
188
        $soft_last = self::checkLastConsonantSoftness($word);
189
        $forms =  array(
190
            Cases::IMENIT => $word,
191
        );
192
193
        // RODIT
194
        $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last || (in_array(S::slice($word, -2, -1), array('г', 'к', 'х'))), $prefix.'и', $prefix.'ы');
195
196
        // DAT
197
        $forms[Cases::DAT] = self::getPredCaseOf12Declensions($word, $last, $prefix);
198
199
        // VINIT
200
        $forms[Cases::VINIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ю', $prefix.'у');
201
202
        // TVORIT
203
        if ($last == 'ь') {
204
            $forms[Cases::TVORIT] = $prefix.'ой';
205 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
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...
206
            $forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой');
207
        }
208
209
        // 	if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last)) || self::checkLastConsonantSoftness($word))
210
        // 	$forms[Cases::TVORIT] = $prefix.'ей';
211
        // else
212
        // 	$forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
213
214
        // PREDLOJ the same as DAT
215
        $forms[Cases::PREDLOJ] = $forms[Cases::DAT];
216
        return $forms;
217
    }
218
219
    /**
220
     * Получение всех форм слова второго склонения.
221
     * @param $word
222
     * @param bool $animateness
223
     * @return array
224
     */
225
    public static function declinateSecondDeclension($word, $animateness = false)
226
    {
227
        $word = S::lower($word);
228
        $last = S::slice($word, -1);
229
        $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) == 'и'));
230
        $prefix = self::getPrefixOfSecondDeclension($word, $last);
231
        $forms =  array(
232
            Cases::IMENIT => $word,
233
        );
234
235
        // RODIT
236
        $forms[Cases::RODIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
237
238
        // DAT
239
        $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у');
240
241
        // VINIT
242
        if (in_array($last, ['о', 'е', 'ё'])) {
243
            $forms[Cases::VINIT] = $word;
244
        } else {
245
            $forms[Cases::VINIT] = self::getVinitCaseByAnimateness($forms, $animateness);
246
        }
247
248
        // TVORIT
249
        // if ($last == 'ь')
250
        // 	$forms[Cases::TVORIT] = $prefix.'ом';
251
        // else if ($last == 'й' || (self::isConsonant($last) && !self::isHissingConsonant($last)))
252
        // 	$forms[Cases::TVORIT] = $prefix.'ем';
253
        // else
254
        // 	$forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant
255
        if (self::isHissingConsonant($last) || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && self::isHissingConsonant(S::slice($word, -2, -1))) || $last == 'ц') {
256
            $forms[Cases::TVORIT] = $prefix.'ем';
257 View Code Duplication
        } elseif (in_array($last, ['й'/*, 'ч', 'щ'*/]) || $soft_last) {
0 ignored issues
show
Duplication introduced by
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...
258
            $forms[Cases::TVORIT] = $prefix.'ем';
259
        } else {
260
            $forms[Cases::TVORIT] = $prefix.'ом';
261
        }
262
263
        // PREDLOJ
264
        $forms[Cases::PREDLOJ] = self::getPredCaseOf12Declensions($word, $last, $prefix);
265
266
        return $forms;
267
    }
268
269
    /**
270
     * Получение всех форм слова третьего склонения.
271
     * @param $word
272
     * @return array
273
     */
274
    public static function declinateThirdDeclension($word)
275
    {
276
        $word = S::lower($word);
277
        $prefix = S::slice($word, 0, -1);
278
        return array(
279
            Cases::IMENIT => $word,
280
            Cases::RODIT => $prefix.'и',
281
            Cases::DAT => $prefix.'и',
282
            Cases::VINIT => $word,
283
            Cases::TVORIT => $prefix.'ью',
284
            Cases::PREDLOJ => $prefix.'и',
285
        );
286
    }
287
288
    /**
289
     * Склонение существительных, образованных от прилагательных и причастий.
290
     * Rules are from http://rusgram.narod.ru/1216-1231.html
291
     * @param $word
292
     * @param $animateness
293
     * @return array
294
     */
295
    public static function declinateAdjective($word, $animateness)
0 ignored issues
show
Unused Code introduced by
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...
296
    {
297
        $prefix = S::slice($word, 0, -2);
298
299
        switch (S::slice($word, -2)) {
300
            // Male adjectives
301
            case 'ой':
302 View Code Duplication
            case 'ый':
0 ignored issues
show
Duplication introduced by
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...
303
                return array(
304
                    Cases::IMENIT => $word,
305
                    Cases::RODIT => $prefix.'ого',
306
                    Cases::DAT => $prefix.'ому',
307
                    Cases::VINIT => $word,
308
                    Cases::TVORIT => $prefix.'ым',
309
                    Cases::PREDLOJ => $prefix.'ом',
310
                );
311
312 View Code Duplication
            case 'ий':
0 ignored issues
show
Duplication introduced by
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...
313
                return array(
314
                    Cases::IMENIT => $word,
315
                    Cases::RODIT => $prefix.'его',
316
                    Cases::DAT => $prefix.'ему',
317
                    Cases::VINIT => $prefix.'его',
318
                    Cases::TVORIT => $prefix.'им',
319
                    Cases::PREDLOJ => $prefix.'ем',
320
                );
321
322
            // Neuter adjectives
323
            case 'ое':
324
            case 'ее':
325
                $prefix = S::slice($word, 0, -1);
326
                return array(
327
                    Cases::IMENIT => $word,
328
                    Cases::RODIT => $prefix.'го',
329
                    Cases::DAT => $prefix.'му',
330
                    Cases::VINIT => $word,
331
                    Cases::TVORIT => S::slice($word, 0, -2).(S::slice($word, -2, -1) == 'о' ? 'ы' : 'и').'м',
332
                    Cases::PREDLOJ => $prefix.'м',
333
                );
334
335
            // Female adjectives
336
            case 'ая':
337
                $ending = self::isHissingConsonant(S::slice($prefix, -1)) ? 'ей' : 'ой';
338
                return array(
339
                    Cases::IMENIT => $word,
340
                    Cases::RODIT => $prefix.$ending,
341
                    Cases::DAT => $prefix.$ending,
342
                    Cases::VINIT => $prefix.'ую',
343
                    Cases::TVORIT => $prefix.$ending,
344
                    Cases::PREDLOJ => $prefix.$ending,
345
                );
346
        }
347
    }
348
349
    /**
350
     * Получение одной формы слова (падежа).
351
     * @param string $word Слово
352
     * @param integer $case Падеж
353
     * @param bool $animateness Признак одушевленности
354
     * @return string
355
     */
356
    public static function getCase($word, $case, $animateness = false)
357
    {
358
        $case = self::canonizeCase($case);
359
        $forms = self::getCases($word, $animateness);
360
        return $forms[$case];
361
    }
362
363
    /**
364
     * @param $word
365
     * @param $last
366
     * @return bool|string
367
     */
368
    public static function getPrefixOfSecondDeclension($word, $last)
369
    {
370
        // слова с бегающей гласной в корне
371
        if (in_array($word, self::$masculineWithSoftAndRunAwayVowels)) {
372
            $prefix = S::slice($word, 0, -3).S::slice($word, -2, -1);
373 View Code Duplication
        } elseif (in_array($last, ['о', 'е', 'ё', 'ь', 'й'])) {
0 ignored issues
show
Duplication introduced by
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...
374
            $prefix = S::slice($word, 0, -1);
375
        }
376
        // уменьшительные формы слов (котенок) и слова с суффиксом ок
377
        elseif (S::slice($word, -2) == 'ок' && S::length($word) > 3) {
378
            $prefix = S::slice($word, 0, -2).'к';
379
        } else {
380
            $prefix = $word;
381
        }
382
        return $prefix;
383
    }
384
385
    /**
386
     * @param array $forms
387
     * @param $animate
388
     * @return mixed
389
     */
390
    public static function getVinitCaseByAnimateness(array $forms, $animate)
391
    {
392
        if ($animate) {
393
            return $forms[Cases::RODIT];
394
        } else {
395
            return $forms[Cases::IMENIT];
396
        }
397
    }
398
399
    /**
400
     * @param $word
401
     * @param $last
402
     * @param $prefix
403
     * @return string
404
     */
405
    public static function getPredCaseOf12Declensions($word, $last, $prefix)
406
    {
407
        if (in_array(S::slice($word, -2), array('ий', 'ие'))) {
408
            if ($last == 'ё') {
409
                return $prefix.'е';
410
            } else {
411
                return $prefix.'и';
412
            }
413
        } else {
414
            return $prefix.'е';
415
        }
416
    }
417
}
418