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

NounPluralization::pluralize()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 25
Code Lines 15

Duplication

Lines 3
Ratio 12 %

Importance

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