Completed
Push — master ( 0d947c...778e4d )
by f
01:51
created

src/Russian/FirstNamesInflection.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://www.imena.org/decl_mn.html / http://www.imena.org/decl_fn.html
8
 * and http://rus.omgpu.ru/2016/04/18/%D1%81%D0%BA%D0%BB%D0%BE%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5-%D0%BB%D0%B8%D1%87%D0%BD%D1%8B%D1%85-%D0%B8%D0%BC%D1%91%D0%BD/
9
 */
10
class FirstNamesInflection extends \morphos\NamesInflection implements Cases
11
{
12
    use RussianLanguage, CasesHelper;
13
14
    protected static $exceptions = [
15
        'лев' => [
16
            self::IMENIT => 'Лев',
17
            self::RODIT => 'Льва',
18
            self::DAT => 'Льву',
19
            self::VINIT => 'Льва',
20
            self::TVORIT => 'Львом',
21
            self::PREDLOJ => 'Льве',
22
        ],
23
        'павел' => [
24
            self::IMENIT => 'Павел',
25
            self::RODIT => 'Павла',
26
            self::DAT => 'Павлу',
27
            self::VINIT => 'Павла',
28
            self::TVORIT => 'Павлом',
29
            self::PREDLOJ => 'Павле',
30
        ]
31
    ];
32
33
    protected static $menNames = [
34
        'абрам', 'аверьян', 'авраам', 'агафон', 'адам', 'азар', 'акакий', 'аким', 'аксён', 'александр', 'алексей',
35
        'альберт', 'анатолий', 'андрей', 'андрон', 'антип', 'антон', 'аполлон', 'аристарх', 'аркадий', 'арнольд',
36
        'арсений', 'арсентий', 'артем', 'артём', 'артемий', 'артур', 'аскольд', 'афанасий', 'богдан', 'борис',
37
        'борислав', 'бронислав', 'вадим', 'валентин', 'валерий', 'варлам', 'василий', 'венедикт', 'вениамин',
38
        'веньямин', 'венцеслав', 'виктор', 'виген', 'вилен', 'виталий', 'владилен', 'владимир', 'владислав', 'владлен',
39
        'вова', 'всеволод', 'всеслав', 'вячеслав', 'гавриил', 'геннадий', 'георгий', 'герман', 'глеб', 'григорий',
40
        'давид', 'даниил', 'данил', 'данила', 'демьян', 'денис', 'димитрий', 'дмитрий', 'добрыня', 'евгений', 'евдоким',
41
        'евсей', 'егор', 'емельян', 'еремей', 'ермолай', 'ерофей', 'ефим', 'захар', 'иван', 'игнат', 'игорь',
42
        'илларион', 'иларион', 'илья', 'иосиф', 'казимир', 'касьян', 'кирилл', 'кондрат', 'константин', 'кузьма',
43
        'лавр', 'лаврентий', 'лазарь', 'ларион', 'лев', 'леонард', 'леонид', 'лука', 'максим', 'марат', 'мартын',
44
        'матвей', 'мефодий', 'мирон', 'михаил', 'моисей', 'назар', 'никита', 'николай', 'олег', 'осип', 'остап',
45
        'павел', 'панкрат', 'пантелей', 'парамон', 'пётр', 'петр', 'платон', 'потап', 'прохор', 'роберт', 'ростислав',
46
        'савва', 'савелий', 'семён', 'семен', 'сергей', 'сидор', 'спартак', 'тарас', 'терентий', 'тимофей', 'тимур',
47
        'тихон', 'ульян', 'фёдор', 'федор', 'федот', 'феликс', 'фирс', 'фома', 'харитон', 'харлам', 'эдуард',
48
        'эммануил', 'эраст', 'юлиан', 'юлий', 'юрий', 'яков', 'ян', 'ярослав',
49
    ];
50
51
    protected static $womenNames = [
52
        'авдотья', 'аврора', 'агата', 'агния', 'агриппина', 'ада', 'аксинья', 'алевтина', 'александра', 'алёна',
53
        'алена', 'алина', 'алиса', 'алла', 'альбина', 'амалия', 'анастасия', 'ангелина', 'анжела', 'анжелика', 'анна',
54
        'антонина', 'анфиса', 'арина', 'белла', 'божена', 'валентина', 'валерия', 'ванда', 'варвара', 'василина',
55
        'василиса', 'вера', 'вероника', 'виктория', 'виола', 'виолетта', 'вита', 'виталия', 'владислава', 'власта',
56
        'галина', 'глафира', 'дарья', 'диана', 'дина', 'ева', 'евгения', 'евдокия', 'евлампия', 'екатерина', 'елена',
57
        'елизавета', 'ефросиния', 'ефросинья', 'жанна', 'зиновия', 'злата', 'зоя', 'ивонна', 'изольда', 'илона', 'инга',
58
        'инесса', 'инна', 'ирина', 'ия', 'капитолина', 'карина', 'каролина', 'кира', 'клавдия', 'клара', 'клеопатра',
59
        'кристина', 'ксения', 'лада', 'лариса', 'лиана', 'лидия', 'лилия', 'лина', 'лия', 'лора', 'любава', 'любовь',
60
        'людмила', 'майя', 'маргарита', 'марианна', 'мариетта', 'марина', 'мария', 'марья', 'марта', 'марфа', 'марьяна',
61
        'матрёна', 'матрена', 'матрона', 'милена', 'милослава', 'мирослава', 'муза', 'надежда', 'настасия', 'настасья',
62
        'наталия', 'наталья', 'нелли', 'ника', 'нина', 'нинель', 'нонна', 'оксана', 'олимпиада', 'ольга', 'пелагея',
63
        'полина', 'прасковья', 'раиса', 'рената', 'римма', 'роза', 'роксана', 'руфь', 'сарра', 'светлана', 'серафима',
64
        'снежана', 'софья', 'софия', 'стелла', 'степанида', 'стефания', 'таисия', 'таисья', 'тамара', 'татьяна',
65
        'ульяна', 'устиния', 'устинья', 'фаина', 'фёкла', 'фекла', 'феодора', 'хаврония', 'христина', 'эвелина',
66
        'эдита', 'элеонора', 'элла', 'эльвира', 'эмилия', 'эмма', 'юдифь', 'юлиана', 'юлия', 'ядвига', 'яна',
67
        'ярослава',
68
    ];
69
70
    protected static $immutableNames = [
71
        'николя',
72
    ];
73
74
    /**
75
     * Checks if name is mutable
76
     * @param string $name
77
     * @param null|string $gender
78
     * @return bool
79
     */
80 716
    public static function isMutable($name, $gender = null)
81
    {
82 716
        $name = S::lower($name);
83
84 716
        if (in_array($name, static::$immutableNames, true)) {
85
            return false;
86
        }
87
88 716
        if ($gender === null) {
89
            $gender = static::detectGender($name);
90
        }
91
92
        // man rules
93 716
        if ($gender === static::MALE) {
94
            // soft consonant
95 451
            if (S::lower(S::slice($name, -1)) == 'ь' && static::isConsonant(S::slice($name, -2, -1))) {
96 14
                return true;
97 437
            } elseif (in_array(S::slice($name, -1), array_diff(static::$consonants, ['й', /*'Ч', 'Щ'*/]), true)) { // hard consonant
98 259
                return true;
99 178
            } elseif (S::slice($name, -1) == 'й') {
100 104
                return true;
101 74 View Code Duplication
            } else if (in_array(S::slice($name, -2), ['ло', 'ко'], true)) {
102 74
                return true;
103
            }
104 265
        } else if ($gender === static::FEMALE) {
105
            // soft consonant
106 265
            if (S::lower(S::slice($name, -1)) == 'ь' && static::isConsonant(S::slice($name, -2, -1))) {
107 4
                return true;
108 261
            } else if (static::isHissingConsonant(S::slice($name, -1))) {
109 5
                return true;
110
            }
111
        }
112
113
        // common rules
114 326
        if ((in_array(S::slice($name, -1), ['а', 'я']) && !static::isVowel(S::slice($name, -2, -1))) || in_array(S::slice($name, -2), ['ия', 'ья', 'ея', 'оя'], true)) {
115 275
            return true;
116
        }
117
118 51
        return false;
119
    }
120
121
    /**
122
     * @param $name
123
     * @return string
124
     */
125 510
    public static function detectGender($name)
126
    {
127 510
        $name = S::lower($name);
128 510
        if (in_array($name, static::$menNames, true)) {
129 121
            return static::MALE;
130 390
        } elseif (in_array($name, static::$womenNames, true)) {
131 111
            return static::FEMALE;
132
        }
133
134 279
        $man = $woman = 0;
135 279
        $last1 = S::slice($name, -1);
136 279
        $last2 = S::slice($name, -2);
137 279
        $last3 = S::slice($name, -3);
138
139
        // try to detect gender by some statistical rules
140
        //
141 279
        if ($last1 == 'й') {
142 48
            $man += 0.9;
143
        }
144 279
        if ($last1 == 'ь') {
145
            $man += 0.02;
146
        }
147 279
        if (in_array($last1, static::$consonants, true)) {
148 199
            $man += 0.01;
149
        }
150 279
        if (in_array($last2, ['он', 'ов', 'ав', 'ам', 'ол', 'ан', 'рд', 'мп'], true)) {
151 47
            $man += 0.3;
152
        }
153 279
        if (in_array($last2, ['вь', 'фь', 'ль'], true)) {
154
            $woman += 0.1;
155
        }
156 279
        if (in_array($last2, ['ла'], true)) {
157 4
            $woman += 0.04;
158
        }
159 279
        if (in_array($last2, ['то', 'ма'], true)) {
160
            $man += 0.01;
161
        }
162 279 View Code Duplication
        if (in_array($last3, ['лья', 'вва', 'ока', 'ука', 'ита'], true)) {
163 2
            $man += 0.2;
164
        }
165 279
        if (in_array($last3, ['има'], true)) {
166
            $woman += 0.15;
167
        }
168 279 View Code Duplication
        if (in_array($last3, ['лия', 'ния', 'сия', 'дра', 'лла', 'кла', 'опа'], true)) {
169 3
            $woman += 0.5;
170
        }
171 279 View Code Duplication
        if (in_array(S::slice($name, -4), ['льда', 'фира', 'нина', 'лита', 'алья'], true)) {
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...
172
            $woman += 0.5;
173
        }
174
175 279
        return $man === $woman ? null
176 279
            : ($man > $woman ? static::MALE : static::FEMALE);
177
    }
178
179
    /**
180
     * @param string $name
181
     * @param null|string $gender
182
     * @return array
183
     */
184 140
    public static function getCases($name, $gender = null)
185
    {
186 140
        $name = S::lower($name);
187
188 140
        if (static::isMutable($name, $gender)) {
189
            // common rules for ия and я
190 140
            if (S::slice($name, -2) == 'ия') {
191 20
                $prefix = S::name(S::slice($name, 0, -1));
192
                return [
193 20
                    static::IMENIT => $prefix.'я',
194 20
                    static::RODIT => $prefix.'и',
195 20
                    static::DAT => $prefix.'и',
196 20
                    static::VINIT => $prefix.'ю',
197 20
                    static::TVORIT => $prefix.'ей',
198 20
                    static::PREDLOJ => $prefix.'и',
199
                ];
200 120 View Code Duplication
            } elseif (S::slice($name, -1) == 'я') {
201 10
                $prefix = S::name(S::slice($name, 0, -1));
202
                return [
203 10
                    static::IMENIT => $prefix.'я',
204 10
                    static::RODIT => $prefix.'и',
205 10
                    static::DAT => $prefix.'е',
206 10
                    static::VINIT => $prefix.'ю',
207 10
                    static::TVORIT => $prefix.'ей',
208 10
                    static::PREDLOJ => $prefix.'е',
209
                ];
210
            }
211
212 110
            if (!in_array($name, static::$immutableNames, true)) {
213 110
                if ($gender === null) {
214
                    $gender = static::detectGender($name);
215
                }
216 110
                if ($gender === static::MALE || $name === 'саша') {
217 84
                    if (($result = static::getCasesMan($name)) !== null) {
218 84
                        return $result;
219
                    }
220 26
                } elseif ($gender === static::FEMALE) {
221 26
                    if (($result = static::getCasesWoman($name)) !== null) {
222 26
                        return $result;
223
                    }
224
                }
225
            }
226
        }
227
228
        $name = S::name($name);
229
        return array_fill_keys(array(static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ), $name);
230
    }
231
232
    /**
233
     * @param string $name
234
     * @return array|null
235
     */
236 84
    protected static function getCasesMan($name)
237
    {
238
        // special cases for Лев, Павел
239 84
        if (isset(static::$exceptions[$name])) {
240
            return static::$exceptions[$name];
241 84
        } elseif (in_array(S::slice($name, -1), array_diff(static::$consonants, ['й', /*'Ч', 'Щ'*/]), true)) { // hard consonant
242 24
			if (in_array(S::slice($name, -2), ['ек', 'ёк'], true)) { // Витек, Санек
243
                // case for foreign names like Салмонбек
244 6
                if (static::isConsonant(S::slice($name, -4, -3)))
245 2
                    $prefix = S::name(S::slice($name, 0, -2)).'ек';
246
                else
247 6
				    $prefix = S::name(S::slice($name, 0, -2)).'ьк';
248
			} else {
249 18
                if ($name === 'пётр')
250
                    $prefix = S::name(str_replace('ё', 'е', $name));
251
                else
252 18
				    $prefix = S::name($name);
253
            }
254
            return [
255 24
                static::IMENIT => S::name($name),
256 24
                static::RODIT => $prefix.'а',
257 24
                static::DAT => $prefix.'у',
258 24
                static::VINIT => $prefix.'а',
259 24
                static::TVORIT => RussianLanguage::isHissingConsonant(S::slice($name, -1)) || S::slice($name, -1) == 'ц' ? $prefix.'ем' : $prefix.'ом',
260 24
                static::PREDLOJ => $prefix.'е',
261
            ];
262 60 View Code Duplication
        } elseif (S::slice($name, -1) == 'ь' && static::isConsonant(S::slice($name, -2, -1))) { // soft consonant
263 10
            $prefix = S::name(S::slice($name, 0, -1));
264
            return [
265 10
                static::IMENIT => $prefix.'ь',
266 10
                static::RODIT => $prefix.'я',
267 10
                static::DAT => $prefix.'ю',
268 10
                static::VINIT => $prefix.'я',
269 10
                static::TVORIT => $prefix.'ем',
270 10
                static::PREDLOJ => $prefix.'е',
271
            ];
272 50
        } elseif (in_array(S::slice($name, -2), ['ай', 'ей', 'ой', 'уй', 'яй', 'юй', 'ий'], true)) {
273 24
            $prefix = S::name(S::slice($name, 0, -1));
274 24
            $postfix = S::slice($name, -2) == 'ий' ? 'и' : 'е';
275
            return [
276 24
                static::IMENIT => $prefix.'й',
277 24
                static::RODIT => $prefix.'я',
278 24
                static::DAT => $prefix.'ю',
279 24
                static::VINIT => $prefix.'я',
280 24
                static::TVORIT => $prefix.'ем',
281 24
                static::PREDLOJ => $prefix.$postfix,
282
            ];
283 26
        } elseif (S::slice($name, -1) == 'а' && static::isConsonant($before = S::slice($name, -2, -1)) && !in_array($before, [/*'г', 'к', 'х', */'ц'], true)) {
284 22
            $prefix = S::name(S::slice($name, 0, -1));
285 22
            $postfix = (RussianLanguage::isHissingConsonant($before) || in_array($before, ['г', 'к', 'х'], true)) ? 'и' : 'ы';
286
            return [
287 22
                static::IMENIT => $prefix.'а',
288 22
                static::RODIT => $prefix.$postfix,
289 22
                static::DAT => $prefix.'е',
290 22
                static::VINIT => $prefix.'у',
291 22
                static::TVORIT => $prefix.($before === 'ш' ? 'е' : 'о').'й',
292 22
                static::PREDLOJ => $prefix.'е',
293
            ];
294 4
        } elseif (S::slice($name, -2) == 'ло' || S::slice($name, -2) == 'ко') {
295 4
            $prefix = S::name(S::slice($name, 0, -1));
296 4
            $postfix = S::slice($name, -2, -1) == 'к' ? 'и' : 'ы';
297
            return [
298 4
                static::IMENIT => $prefix.'о',
299 4
                static::RODIT =>  $prefix.$postfix,
300 4
                static::DAT => $prefix.'е',
301 4
                static::VINIT => $prefix.'у',
302 4
                static::TVORIT => $prefix.'ой',
303 4
                static::PREDLOJ => $prefix.'е',
304
            ];
305
        }
306
307
        return null;
308
    }
309
310
    /**
311
     * @param string $name
312
     * @return array|null
313
     */
314 26
    protected static function getCasesWoman($name)
315
    {
316 26
        if (S::slice($name, -1) == 'а' && !static::isVowel($before = (S::slice($name, -2, -1)))) {
317 17
            $prefix = S::name(S::slice($name, 0, -1));
318 17
            if ($before != 'ц') {
319 14
                $postfix = (RussianLanguage::isHissingConsonant($before) || in_array($before, ['г', 'к', 'х'], true)) ? 'и' : 'ы';
320
                return [
321 14
                    static::IMENIT => $prefix.'а',
322 14
                    static::RODIT => $prefix.$postfix,
323 14
                    static::DAT => $prefix.'е',
324 14
                    static::VINIT => $prefix.'у',
325 14
                    static::TVORIT => $prefix.'ой',
326 14
                    static::PREDLOJ => $prefix.'е',
327
                ];
328 View Code Duplication
            } else {
329
                return [
330 3
                    static::IMENIT => $prefix.'а',
331 3
                    static::RODIT => $prefix.'ы',
332 3
                    static::DAT => $prefix.'е',
333 3
                    static::VINIT => $prefix.'у',
334 3
                    static::TVORIT => $prefix.'ей',
335 3
                    static::PREDLOJ => $prefix.'е',
336
                ];
337
            }
338 9 View Code Duplication
        } elseif (S::slice($name, -1) == 'ь' && static::isConsonant(S::slice($name, -2, -1))) {
339 4
            $prefix = S::name(S::slice($name, 0, -1));
340
            return [
341 4
                static::IMENIT => $prefix.'ь',
342 4
                static::RODIT => $prefix.'и',
343 4
                static::DAT => $prefix.'и',
344 4
                static::VINIT => $prefix.'ь',
345 4
                static::TVORIT => $prefix.'ью',
346 4
                static::PREDLOJ => $prefix.'и',
347
            ];
348 5
        } elseif (RussianLanguage::isHissingConsonant(S::slice($name, -1))) {
349 5
            $prefix = S::name($name);
350
            return [
351 5
                static::IMENIT => $prefix,
352 5
                static::RODIT => $prefix.'и',
353 5
                static::DAT => $prefix.'и',
354 5
                static::VINIT => $prefix,
355 5
                static::TVORIT => $prefix.'ью',
356 5
                static::PREDLOJ => $prefix.'и',
357
            ];
358
        }
359
        return null;
360
    }
361
362
    /**
363
     * @param string $name
364
     * @param string $case
365
     * @param null|string $gender
366
     * @return string
367
     * @throws \Exception
368
     */
369 52
    public static function getCase($name, $case, $gender = null)
370
    {
371 52
        $case = static::canonizeCase($case);
372 52
        $forms = static::getCases($name, $gender);
373 52
        return $forms[$case];
374
    }
375
}
376