Completed
Push — master ( 093ce2...15bafb )
by f
06:55
created

src/Russian/NounPluralization.php (2 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\S;
5
6
/**
7
 * Rules are from http://morpher.ru/Russian/Noun.aspx
8
 */
9
class NounPluralization extends \morphos\NounPluralization implements Cases
10
{
11
    use RussianLanguage, CasesHelper;
12
13
    const ONE = 1;
14
    const TWO_FOUR = 2;
15
    const FIVE_OTHER = 3;
16
17
    protected static $neuterExceptions = [
18
        'поле',
19
        'море',
20
    ];
21
22
    protected static $genitiveExceptions = [
23
        'письмо' => 'писем',
24
        'пятно' => 'пятен',
25
        'кресло' => 'кресел',
26
        'коромысло' => 'коромысел',
27
        'ядро' => 'ядер',
28
        'блюдце' => 'блюдец',
29
        'полотенце' => 'полотенец',
30
        'гривна' => 'гривен',
31
        'год' => 'лет',
32
    ];
33
34
    protected static $immutableWords = [
35
        'евро',
36
        'пенни',
37
    ];
38
39
    protected static $runawayVowelsExceptions = [
40
        'писе*ц',
41
        'песе*ц',
42
        'глото*к',
43
    ];
44
45
    /**
46
     * @return array|bool
47
     */
48 74 View Code Duplication
    protected static function getRunAwayVowelsList()
49
    {
50 74
        $runawayVowelsNormalized = [];
51 74
        foreach (self::$runawayVowelsExceptions as $word) {
52 74
            $runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
53
        }
54 74
        return $runawayVowelsNormalized;
55
    }
56
57
    /**
58
     * Склонение существительного для сочетания с числом (кол-вом предметов).
59
     *
60
     * @param string|int $word        Название предмета
61
     * @param int|string $count       Количество предметов
62
     * @param bool       $animateness Признак одушевленности
63
     * @param string     $case        Род существительного
64
     *
65
     * @return string
66
     * @throws \Exception
67
     */
68 52
    public static function pluralize($word, $count = 2, $animateness = false, $case = null)
69
    {
70
        // меняем местами аргументы, если они переданы в старом формате
71 52 View Code Duplication
        if (is_string($count) && is_numeric($word)) {
72 15
            list($count, $word) = [$word, $count];
73
        }
74
75 52
        if ($case !== null)
76 2
            $case = self::canonizeCase($case);
77
78
        // для адъективных существительных правила склонения проще:
79
        // только две формы
80 52 View Code Duplication
        if (self::isAdjectiveNoun($word)) {
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...
81 3
            if (self::getNumeralForm($count) == self::ONE)
82 2
                return $word;
83
            else
84 3
                return NounPluralization::getCase($word,
85 3
                    $case !== null
86
                        ? $case
87 3
                        : self::RODIT, $animateness);
88
        }
89
90 50
        if ($case === null) {
91 48
            switch (self::getNumeralForm($count)) {
92 48
                case self::ONE:
93 28
                    return $word;
94 48
                case self::TWO_FOUR:
95 34
                    return NounDeclension::getCase($word, self::RODIT, $animateness);
96 36
                case self::FIVE_OTHER:
97 36
                    return NounPluralization::getCase($word, self::RODIT, $animateness);
98
            }
99
        }
100
101 2 View Code Duplication
        if (self::getNumeralForm($count) == self::ONE)
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...
102 2
            return NounDeclension::getCase($word, $case, $animateness);
103
        else
104 2
            return NounPluralization::getCase($word, $case, $animateness);
105
    }
106
107
    /**
108
     * @param $count
109
     * @return int
110
     */
111 64
    public static function getNumeralForm($count)
112
    {
113 64
        if ($count > 100) {
114 41
            $count %= 100;
115
        }
116 64
        $ending = $count % 10;
117
118 64
        if (($count > 20 && $ending == 1) || $count == 1) {
119 36
            return self::ONE;
120 63
        } elseif (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) {
121 44
            return self::TWO_FOUR;
122
        } else {
123 48
            return self::FIVE_OTHER;
124
        }
125
    }
126
127
    /**
128
     * @param $word
129
     * @param $case
130
     * @param bool $animateness
131
     * @return string
132
     * @throws \Exception
133
     */
134 40
    public static function getCase($word, $case, $animateness = false)
135
    {
136 40
        $case = self::canonizeCase($case);
137 40
        $forms = self::getCases($word, $animateness);
138 40
        return $forms[$case];
139
    }
140
141
    /**
142
     * @param $word
143
     * @param bool $animateness
144
     * @return array
145
     */
146 84
    public static function getCases($word, $animateness = false)
147
    {
148 84
        $word = S::lower($word);
149
150 84
        if (in_array($word, self::$immutableWords, true)) {
151
            return [
152 1
                self::IMENIT => $word,
153 1
                self::RODIT => $word,
154 1
                self::DAT => $word,
155 1
                self::VINIT => $word,
156 1
                self::TVORIT => $word,
157 1
                self::PREDLOJ => $word,
158
            ];
159
        }
160
161
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
162 84
        if (self::isAdjectiveNoun($word)) {
163 11
            return self::declinateAdjective($word, $animateness);
164
        }
165
166
        // Субстантивное склонение (существительные)
167 74
        return self::declinateSubstative($word, $animateness);
168
    }
169
170
    /**
171
     * Склонение обычных существительных.
172
     * @param $word
173
     * @param $animateness
174
     * @return array
175
     */
176 74
    protected static function declinateSubstative($word, $animateness)
177
    {
178 74
        $prefix = S::slice($word, 0, -1);
179 74
        $last = S::slice($word, -1);
180
181 74
        $runaway_vowels_list = static::getRunAwayVowelsList();
182 74
        if (isset($runaway_vowels_list[$word])) {
183 1
            $vowel_offset = $runaway_vowels_list[$word];
184 1
            $word = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1);
185
        }
186
187 74
        if (($declension = NounDeclension::getDeclension($word)) == NounDeclension::SECOND_DECLENSION) {
188 47
            $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
189
                    && ((
190 13
                        self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1)))
191 47
                        || S::slice($word, -2, -1) == 'и'));
192 47
            $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last);
193 33
        } elseif ($declension == NounDeclension::FIRST_DECLENSION) {
194 29
            $soft_last = self::checkLastConsonantSoftness($word);
195
        } else {
196 4
            $soft_last = in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true);
197
        }
198
199 74
        $forms = [];
200
201 74
        if (in_array($last, ['ч', 'г'], false) || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)
202 74
            || (self::isVowel($last) && in_array(S::slice($word, -2, -1), ['ч', 'к'], true))) { // before ч, чь, сь, ч+vowel, к+vowel
203 32
            $forms[Cases::IMENIT] = $prefix.'и';
204 47
        } elseif (in_array($last, ['н', 'ц', 'р', 'т'], true)) {
205 17
            $forms[Cases::IMENIT] = $prefix.'ы';
206 View Code Duplication
        } else {
207 33
            $forms[Cases::IMENIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
208
        }
209
210
        // RODIT
211 74
        if (isset(self::$genitiveExceptions[$word])) {
212 10
            $forms[Cases::RODIT] = self::$genitiveExceptions[$word];
213 66
        } elseif (in_array($last, ['о', 'е'], true)) {
214
            // exceptions
215 8
            if (in_array($word, self::$neuterExceptions, true)) {
216 3
                $forms[Cases::RODIT] = $prefix.'ей';
217 5 View Code Duplication
            } elseif (S::slice($word, -2, -1) == 'и') {
218 2
                $forms[Cases::RODIT] = $prefix.'й';
219
            } else {
220 8
                $forms[Cases::RODIT] = $prefix;
221
            }
222 58
        } elseif (S::slice($word, -2) == 'ка' && S::slice($word, -3, -2) !== 'и') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка
223 7
            if (S::slice($word, -3, -2) == 'л') {
224 2
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок';
225 5 View Code Duplication
            } elseif (S::slice($word, -3, -2) == 'й') {
226 3
                $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек';
227
            } else {
228 7
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек';
229
            }
230 51
        } elseif (in_array($last, ['а'], true)) { // обида, ябеда
231 20
            $forms[Cases::RODIT] = $prefix;
232 37 View Code Duplication
        } elseif (in_array($last, ['я'], true)) { // молния
233 1
            $forms[Cases::RODIT] = $prefix.'й';
234 36
        } elseif (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)) {
235 13
            $forms[Cases::RODIT] = $prefix.'ей';
236 25 View Code Duplication
        } elseif ($last == 'й' || S::slice($word, -2) == 'яц') { // месяц
237 7
            $forms[Cases::RODIT] = $prefix.'ев';
238
        } else { // (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last))
239 18
            $forms[Cases::RODIT] = $prefix.'ов';
240
        }
241
242
        // DAT
243 74
        $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам');
244
245
        // VINIT
246 74
        $forms[Cases::VINIT] = NounDeclension::getVinitCaseByAnimateness($forms, $animateness);
247
248
        // TVORIT
249
        // my personal rule
250 74
        if ($last == 'ь' && $declension == NounDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)) {
251
            $forms[Cases::TVORIT] = $prefix.'ми';
252
        } else {
253 74
            $forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами');
254
        }
255
256
        // PREDLOJ
257 74
        $forms[Cases::PREDLOJ] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах');
258 74
        return $forms;
259
    }
260
261
    /**
262
     * Склонение существительных, образованных от прилагательных и причастий.
263
     * Rules are from http://rusgram.narod.ru/1216-1231.html
264
     * @param $word
265
     * @param $animateness
266
     * @return array
267
     */
268 11
    protected static function declinateAdjective($word, $animateness)
269
    {
270 11
        $prefix = S::slice($word, 0, -2);
271 11
        $vowel = self::isHissingConsonant(S::slice($prefix, -1)) ? 'и' : 'ы';
272
        return [
273 11
            Cases::IMENIT => $prefix.$vowel.'е',
274 11
            Cases::RODIT => $prefix.$vowel.'х',
275 11
            Cases::DAT => $prefix.$vowel.'м',
276 11
            Cases::VINIT => $prefix.$vowel.($animateness ? 'х' : 'е'),
277 11
            Cases::TVORIT => $prefix.$vowel.'ми',
278 11
            Cases::PREDLOJ => $prefix.$vowel.'х',
279
        ];
280
    }
281
}
282