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

src/Russian/NounPluralization.php (1 issue)

assigning incompatible types to properties.

Bug Documentation Major

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
    protected static $runawayVowelsNormalized = false;
46
47
    /**
48
     * @return array|bool
49
     */
50 67
    protected static function getRunAwayVowelsList()
51
    {
52 67
        if (self::$runawayVowelsNormalized === false) {
53 1
            self::$runawayVowelsNormalized = [];
0 ignored issues
show
Documentation Bug introduced by
It seems like array() of type array is incompatible with the declared type boolean of property $runawayVowelsNormalized.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

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