Completed
Push — master ( b5bb9d...38f7c0 )
by f
02:02
created

src/Russian/NounPluralization.php (1 issue)

Labels
Severity

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 69 View Code Duplication
    protected static function getRunAwayVowelsList()
49
    {
50 69
        $runawayVowelsNormalized = [];
51 69
        foreach (self::$runawayVowelsExceptions as $word) {
52 69
            $runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
53
        }
54 69
        return $runawayVowelsNormalized;
55
    }
56
57
    /**
58
     * Склонение существительного для сочетания с числом (кол-вом предметов).
59
     * @param int|string $count Количество предметов
60
     * @param string|int $word Название предмета
61
     * @param bool $animateness Признак одушевленности
62
     * @return string
63
     * @throws \Exception
64
     */
65 50
    public static function pluralize($word, $count = 2, $animateness = false)
66
    {
67
        // меняем местами аргументы, если они переданы в старом формате
68 50 View Code Duplication
        if (is_string($count) && is_numeric($word)) {
69 15
            list($count, $word) = [$word, $count];
70
        }
71
72
        // для адъективных существительных правила склонения проще:
73
        // только две формы
74 50
        if (self::isAdjectiveNoun($word)) {
75 3
            if (self::getNumeralForm($count) == self::ONE)
76 2
                return $word;
77
            else
78 3
                return NounPluralization::getCase($word, self::RODIT, $animateness);
79
        }
80
81 48
        switch (self::getNumeralForm($count)) {
82 48
            case self::ONE:
83 28
                return $word;
84 48
            case self::TWO_FOUR:
85 34
                return NounDeclension::getCase($word, self::RODIT, $animateness);
86 36
            case self::FIVE_OTHER:
87
                // special case for YEAR >= 5
88 36
                if ($word === 'год') {
89 4
                    return 'лет';
90
                }
91
92 34
                return NounPluralization::getCase($word, self::RODIT, $animateness);
93
        }
94
    }
95
96
    /**
97
     * @param $count
98
     * @return int
99
     */
100 62
    public static function getNumeralForm($count)
101
    {
102 62
        if ($count > 100) {
103 39
            $count %= 100;
104
        }
105 62
        $ending = $count % 10;
106
107 62
        if (($count > 20 && $ending == 1) || $count == 1) {
108 34
            return self::ONE;
109 61
        } elseif (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) {
110 42
            return self::TWO_FOUR;
111
        } else {
112 46
            return self::FIVE_OTHER;
113
        }
114
    }
115
116
    /**
117
     * @param $word
118
     * @param $case
119
     * @param bool $animateness
120
     * @return string
121
     * @throws \Exception
122
     */
123 36
    public static function getCase($word, $case, $animateness = false)
124
    {
125 36
        $case = self::canonizeCase($case);
126 36
        $forms = self::getCases($word, $animateness);
127 36
        return $forms[$case];
128
    }
129
130
    /**
131
     * @param $word
132
     * @param bool $animateness
133
     * @return array
134
     */
135 79
    public static function getCases($word, $animateness = false)
136
    {
137 79
        $word = S::lower($word);
138
139 79
        if (in_array($word, self::$immutableWords, true)) {
140
            return [
141 1
                self::IMENIT => $word,
142 1
                self::RODIT => $word,
143 1
                self::DAT => $word,
144 1
                self::VINIT => $word,
145 1
                self::TVORIT => $word,
146 1
                self::PREDLOJ => $word,
147
            ];
148
        }
149
150
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
151 79
        if (self::isAdjectiveNoun($word)) {
0 ignored issues
show
It seems like $word defined by \morphos\S::lower($word) on line 137 can also be of type boolean; however, morphos\Russian\RussianLanguage::isAdjectiveNoun() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152 11
            return self::declinateAdjective($word, $animateness);
153
        }
154
155
        // Субстантивное склонение (существительные)
156 69
        return self::declinateSubstative($word, $animateness);
157
    }
158
159
    /**
160
     * Склонение обычных существительных.
161
     * @param $word
162
     * @param $animateness
163
     * @return array
164
     */
165 69
    protected static function declinateSubstative($word, $animateness)
166
    {
167 69
        $prefix = S::slice($word, 0, -1);
168 69
        $last = S::slice($word, -1);
169
170 69
        $runaway_vowels_list = static::getRunAwayVowelsList();
171 69
        if (isset($runaway_vowels_list[$word])) {
172 1
            $vowel_offset = $runaway_vowels_list[$word];
173 1
            $word = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1);
174
        }
175
176 69
        if (($declension = NounDeclension::getDeclension($word)) == NounDeclension::SECOND_DECLENSION) {
177 42
            $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true)
178
                    && ((
179 12
                        self::isConsonant(S::slice($word, -2, -1)) && !self::isHissingConsonant(S::slice($word, -2, -1)))
180 42
                        || S::slice($word, -2, -1) == 'и'));
181 42
            $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last);
182 33
        } elseif ($declension == NounDeclension::FIRST_DECLENSION) {
183 29
            $soft_last = self::checkLastConsonantSoftness($word);
184
        } else {
185 4
            $soft_last = in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true);
186
        }
187
188 69
        $forms = [];
189
190 69
        if (in_array($last, ['ч', 'г'], false) || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)
191 69
            || (self::isVowel($last) && in_array(S::slice($word, -2, -1), ['ч', 'к'], true))) { // before ч, чь, сь, ч+vowel, к+vowel
192 32
            $forms[Cases::IMENIT] = $prefix.'и';
193 42
        } elseif (in_array($last, ['н', 'ц', 'р', 'т'], true)) {
194 16
            $forms[Cases::IMENIT] = $prefix.'ы';
195 View Code Duplication
        } else {
196 29
            $forms[Cases::IMENIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
197
        }
198
199
        // RODIT
200 69
        if (isset(self::$genitiveExceptions[$word])) {
201 5
            $forms[Cases::RODIT] = self::$genitiveExceptions[$word];
202 64
        } elseif (in_array($last, ['о', 'е'], true)) {
203
            // exceptions
204 8
            if (in_array($word, self::$neuterExceptions, true)) {
205 3
                $forms[Cases::RODIT] = $prefix.'ей';
206 5 View Code Duplication
            } elseif (S::slice($word, -2, -1) == 'и') {
207 2
                $forms[Cases::RODIT] = $prefix.'й';
208
            } else {
209 8
                $forms[Cases::RODIT] = $prefix;
210
            }
211 56
        } elseif (S::slice($word, -2) == 'ка' && S::slice($word, -3, -2) !== 'и') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка
212 7
            if (S::slice($word, -3, -2) == 'л') {
213 2
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок';
214 5 View Code Duplication
            } elseif (S::slice($word, -3, -2) == 'й') {
215 3
                $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек';
216
            } else {
217 7
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек';
218
            }
219 49
        } elseif (in_array($last, ['а'], true)) { // обида, ябеда
220 20
            $forms[Cases::RODIT] = $prefix;
221 35 View Code Duplication
        } elseif (in_array($last, ['я'], true)) { // молния
222 1
            $forms[Cases::RODIT] = $prefix.'й';
223 34
        } elseif (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)) {
224 12
            $forms[Cases::RODIT] = $prefix.'ей';
225 24 View Code Duplication
        } elseif ($last == 'й' || S::slice($word, -2) == 'яц') { // месяц
226 7
            $forms[Cases::RODIT] = $prefix.'ев';
227
        } else { // (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last))
228 17
            $forms[Cases::RODIT] = $prefix.'ов';
229
        }
230
231
        // DAT
232 69
        $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам');
233
234
        // VINIT
235 69
        $forms[Cases::VINIT] = NounDeclension::getVinitCaseByAnimateness($forms, $animateness);
236
237
        // TVORIT
238
        // my personal rule
239 69
        if ($last == 'ь' && $declension == NounDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь'], true)) {
240
            $forms[Cases::TVORIT] = $prefix.'ми';
241
        } else {
242 69
            $forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами');
243
        }
244
245
        // PREDLOJ
246 69
        $forms[Cases::PREDLOJ] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах');
247 69
        return $forms;
248
    }
249
250
    /**
251
     * Склонение существительных, образованных от прилагательных и причастий.
252
     * Rules are from http://rusgram.narod.ru/1216-1231.html
253
     * @param $word
254
     * @param $animateness
255
     * @return array
256
     */
257 11
    protected static function declinateAdjective($word, $animateness)
258
    {
259 11
        $prefix = S::slice($word, 0, -2);
260 11
        $vowel = self::isHissingConsonant(S::slice($prefix, -1)) ? 'и' : 'ы';
261
        return [
262 11
            Cases::IMENIT => $prefix.$vowel.'е',
263 11
            Cases::RODIT => $prefix.$vowel.'х',
264 11
            Cases::DAT => $prefix.$vowel.'м',
265 11
            Cases::VINIT => $prefix.$vowel.($animateness ? 'х' : 'е'),
266 11
            Cases::TVORIT => $prefix.$vowel.'ми',
267 11
            Cases::PREDLOJ => $prefix.$vowel.'х',
268
        ];
269
    }
270
}
271