Completed
Push — master ( 6a872b...95bb87 )
by f
01:14
created

src/Russian/NounPluralization.php (1 issue)

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 = array(
18
        'поле',
19
        'море',
20
    );
21
22
    protected static $genitiveExceptions = array(
23
        'письмо' => 'писем',
24
        'пятно' => 'пятен',
25
        'кресло' => 'кресел',
26
        'коромысло' => 'коромысел',
27
        'ядро' => 'ядер',
28
        'блюдце' => 'блюдец',
29
        'полотенце' => 'полотенец',
30
    );
31
32
    protected static $immutableWords = array(
33
        'евро',
34
        'пенни',
35
    );
36
37
    protected static $runawayVowelsExceptions = array(
38
        'писе*ц',
39
        'песе*ц',
40
        'глото*к',
41
    );
42
43
    protected static $runawayVowelsNormalized = false;
44
45
    protected static function getRunAwayVowelsList()
46
    {
47
        if (self::$runawayVowelsNormalized === false) {
48
            self::$runawayVowelsNormalized = array();
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...
49
            foreach (self::$runawayVowelsExceptions as $word) {
50
                self::$runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
51
            }
52
        }
53
        return self::$runawayVowelsNormalized;
54
    }
55
56
    public static function pluralize($word, $count = 2, $animateness = false)
57
    {
58
        switch (self::getNumeralForm($count)) {
59
            case self::ONE:
60
                return $word;
61
            case self::TWO_FOUR:
62
                return NounDeclension::getCase($word, self::RODIT, $animateness);
63
            case self::FIVE_OTHER:
64
                return NounPluralization::getCase($word, self::RODIT, $animateness);
65
        }
66
    }
67
68
    public static function getNumeralForm($count)
69
    {
70
        if ($count > 100) {
71
            $count %= 100;
72
        }
73
        $ending = $count % 10;
74
        if (($count > 20 && $ending == 1) || $count == 1) {
75
            return self::ONE;
76
        } elseif (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) {
77
            return self::TWO_FOUR;
78
        } else {
79
            return self::FIVE_OTHER;
80
        }
81
    }
82
83
    public static function getCase($word, $case, $animateness = false)
84
    {
85
        $case = self::canonizeCase($case);
86
        $forms = self::getCases($word, $animateness);
87
        return $forms[$case];
88
    }
89
90
    public static function getCases($word, $animateness = false)
91
    {
92
        $word = S::lower($word);
93
94
        if (in_array($word, self::$immutableWords)) {
95
            return array(
96
                self::IMENIT => $word,
97
                self::RODIT => $word,
98
                self::DAT => $word,
99
                self::VINIT => $word,
100
                self::TVORIT => $word,
101
                self::PREDLOJ => self::choosePrepositionByFirstLetter($word, 'об', 'о').' '.$word,
102
            );
103
        }
104
105
        // Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное
106 View Code Duplication
        if (in_array(S::slice($word, -2), array('ой', 'ий', 'ый', 'ая', 'ое', 'ее')) && $word != 'гений') {
107
            return self::declinateAdjective($word, $animateness);
108
        }
109
110
        // Субстантивное склонение (существительные)
111
        return self::declinateSubstative($word, $animateness);
112
    }
113
114
    protected static function declinateSubstative($word, $animateness)
115
    {
116
        $prefix = S::slice($word, 0, -1);
117
        $last = S::slice($word, -1);
118
119
        $runaway_vowels_list = static::getRunAwayVowelsList();
120
        if (isset($runaway_vowels_list[$word])) {
121
            $vowel_offset = $runaway_vowels_list[$word];
122
            $word = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1);
123
        }
124
125
        if (($declension = NounDeclension::getDeclension($word)) == NounDeclension::SECOND_DECLENSION) {
126
            $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) == 'и'));
127
            $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last);
128
        } elseif ($declension == NounDeclension::FIRST_DECLENSION) {
129
            $soft_last = self::checkLastConsonantSoftness($word);
130
        } else {
131
            $soft_last = S::slice($word, -2) == 'сь';
132
        }
133
134
        $forms = array();
135
136
        if ($last == 'ч' || in_array(S::slice($word, -2), array('чь', 'сь')) || (self::isVowel($last) && in_array(S::slice($word, -2, -1), array('ч', 'к')))) { // before ч, чь, сь, ч+vowel, к+vowel
137
            $forms[Cases::IMENIT] = $prefix.'и';
138
        } elseif ($last == 'н' || $last == 'ц') {
139
            $forms[Cases::IMENIT] = $prefix.'ы';
140 View Code Duplication
        } else {
141
            $forms[Cases::IMENIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а');
142
        }
143
144
        // RODIT
145
        if (isset(self::$genitiveExceptions[$word])) {
146
            $forms[Cases::RODIT] = self::$genitiveExceptions[$word];
147
        } elseif (in_array($last, array('о', 'е'))) {
148
            // exceptions
149
            if (in_array($word, self::$neuterExceptions)) {
150
                $forms[Cases::RODIT] = $prefix.'ей';
151 View Code Duplication
            } elseif (S::slice($word, -2, -1) == 'и') {
152
                $forms[Cases::RODIT] = $prefix.'й';
153
            } else {
154
                $forms[Cases::RODIT] = $prefix;
155
            }
156
        } elseif (S::slice($word, -2) == 'ка') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка
157
            if (S::slice($word, -3, -2) == 'л') {
158
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок';
159 View Code Duplication
            } elseif (S::slice($word, -3, -2) == 'й') {
160
                $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек';
161
            } else {
162
                $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек';
163
            }
164
        } elseif (in_array($last, array('а'))) { // обида, ябеда
165
            $forms[Cases::RODIT] = $prefix;
166
        } elseif (in_array($last, array('я'))) { // молния
167
            $forms[Cases::RODIT] = $prefix.'й';
168
        } elseif (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), array('чь', 'сь'))) {
169
            $forms[Cases::RODIT] = $prefix.'ей';
170 View Code Duplication
        } elseif ($last == 'й' || S::slice($word, -2) == 'яц') { // месяц
171
            $forms[Cases::RODIT] = $prefix.'ев';
172
        } else { // (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last))
173
            $forms[Cases::RODIT] = $prefix.'ов';
174
        }
175
176
        // DAT
177
        $forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам');
178
179
        // VINIT
180
        $forms[Cases::VINIT] = NounDeclension::getVinitCaseByAnimateness($forms, $animateness);
181
182
        // TVORIT
183
        // my personal rule
184
        if ($last == 'ь' && $declension == NounDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), array('чь', 'сь'))) {
185
            $forms[Cases::TVORIT] = $prefix.'ми';
186
        } else {
187
            $forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами');
188
        }
189
190
        // PREDLOJ
191
        $forms[Cases::PREDLOJ] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах');
192
        $forms[Cases::PREDLOJ] = self::choosePrepositionByFirstLetter($forms[Cases::PREDLOJ], 'об', 'о').' '.$forms[Cases::PREDLOJ];
193
        return $forms;
194
    }
195
196
    /**
197
     * Rules are from http://rusgram.narod.ru/1216-1231.html
198
     */
199
    protected static function declinateAdjective($word, $animateness)
200
    {
201
        $prefix = S::slice($word, 0, -2);
202
        $vowel = self::isHissingConsonant(S::slice($prefix, -1)) ? 'и' : 'ы';
203
        return array(
204
            Cases::IMENIT => $prefix.$vowel.'е',
205
            Cases::RODIT => $prefix.$vowel.'х',
206
            Cases::DAT => $prefix.$vowel.'м',
207
            Cases::VINIT => $prefix.$vowel.($animateness ? 'х' : 'е'),
208
            Cases::TVORIT => $prefix.$vowel.'ми',
209
            Cases::PREDLOJ => self::choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.$vowel.'х',
210
        );
211
    }
212
}
213