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

src/Russian/GeographicalNamesInflection.php (2 issues)

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: https://ru.wikipedia.org/wiki/%D0%A1%D0%BA%D0%BB%D0%BE%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B3%D0%B5%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9_%D0%B2_%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%BC_%D1%8F%D0%B7%D1%8B%D0%BA%D0%B5
8
 */
9
class GeographicalNamesInflection extends \morphos\BaseInflection implements Cases
10
{
11
    use RussianLanguage, CasesHelper;
12
13
    protected static $abbreviations = [
14
        'сша',
15
        'оаэ',
16
        'ссср',
17
        'юар',
18
    ];
19
20
    protected static $delimiters = [
21
        ' ',
22
        '-на-',
23
        '-',
24
    ];
25
26
    protected static $ovAbnormalExceptions = [
27
        'осташков',
28
    ];
29
30
    protected static $immutableParts = [
31
        'санкт',
32
    ];
33
34
    /**
35
     * Проверяет, склоняемо ли название
36
     * @param string $name Название
37
     * @return bool
38
     */
39 2
    public static function isMutable($name)
40
    {
41 2
        $name = S::lower($name);
42
43
        // // ends with 'ы' or 'и': plural form
44
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
45
        //     return false;
46
47 2
        if (in_array($name, self::$abbreviations, true) || in_array($name, self::$immutableParts, true)) {
48 2
            return false;
49
        }
50
51
        if (strpos($name, ' ') !== false) {
52
            // explode() is not applicable because Geographical unit may have few words
53
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
54
            $last_part = S::slice($name,
55
                S::findLastPosition($name, ' ') + 1);
56
57
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
58
            // N область, N край
59
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
60
                || in_array($last_part, ['край', 'область'], true)) {
61
                return true;
62
            }
63
64
            // пгт N
65
            if ($first_part === 'пгт')
66
                return false;
67
        }
68
69
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
70
        if (in_array(S::slice($name, -1), ['е', 'о'], true) && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
71
            return false;
72
        }
73
        return true;
74
    }
75
76
    /**
77
     * Получение всех форм названия
78
     * @param string $name
79
     * @return array
80
     * @throws \Exception
81
     */
82 33
    public static function getCases($name)
83
    {
84 33
        $name = S::lower($name);
85
86 33
        if (in_array($name, self::$immutableParts, true)) {
87 1
            return array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], S::name($name));
88
        }
89
90 33
        if (strpos($name, ' ') !== false) {
91 8
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
92
            // город N, село N, хутор N, пгт N
93 8
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
94 3
                if ($first_part !== 'пгт')
95 3
                    return self::composeCasesFromWords([
96 3
                        $first_part !== 'республика'
97 2
                            ? NounDeclension::getCases($first_part)
98 3
                            : array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part)),
99 3
                        array_fill_keys(self::getAllCases(), S::name(S::slice($name, S::length($first_part) + 1)))
100
                    ]);
101
                else
102
                    return array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], 'пгт '.S::name(S::slice($name, 4)));
103
            }
104
105 5
            $last_part = S::slice($name,
106 5
                S::findLastPosition($name, ' ') + 1);
107
            // N область, N край
108 5
            if (in_array($last_part, ['край', 'область'], true)) {
109 2
                return self::composeCasesFromWords([static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))), NounDeclension::getCases($last_part)]);
110
            }
111
        }
112
113
        // Сложное название через пробел, '-' или '-на-'
114 30
        foreach (self::$delimiters as $delimiter) {
115 30
            if (strpos($name, $delimiter) !== false) {
116 5
                $parts = explode($delimiter, $name);
117 5
                $result = [];
118 5
                foreach ($parts as $i => $part) {
119 5
                    $result[$i] = static::getCases($part);
120
                }
121 30
                return self::composeCasesFromWords($result, $delimiter);
122
            }
123
        }
124
125 30
        if (!in_array($name, self::$abbreviations, true)) {
126 28
            switch (S::slice($name, -2)) {
127
                // Нижний, Русский
128 28
                case 'ий':
129 2
                    $prefix = S::name(S::slice($name, 0, -2));
130
                    return [
131 2
                        self::IMENIT => $prefix . 'ий',
132 2
                        self::RODIT => $prefix . (self::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
133 2
                        self::DAT => $prefix . (self::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
134 2
                        self::VINIT => $prefix . 'ий',
135 2
                        self::TVORIT => $prefix . 'им',
136 2
                        self::PREDLOJ => $prefix . (self::chooseEndingBySonority($prefix, 'ем', 'ом')),
137
                    ];
138
139
                // Ростовская
140 27
                case 'ая':
141 1
                    $prefix = S::name(S::slice($name, 0, -2));
142
                    return [
143 1
                        self::IMENIT => $prefix . 'ая',
144 1
                        self::RODIT => $prefix . 'ой',
145 1
                        self::DAT => $prefix . 'ой',
146 1
                        self::VINIT => $prefix . 'ую',
147 1
                        self::TVORIT => $prefix . 'ой',
148 1
                        self::PREDLOJ => $prefix . 'ой',
149
                    ];
150
151
                // Грозный, Благодарный
152 26
                case 'ый':
153 2
                    $prefix = S::name(S::slice($name, 0, -2));
154
                    return [
155 2
                        self::IMENIT => $prefix . 'ый',
156 2
                        self::RODIT => $prefix . 'ого',
157 2
                        self::DAT => $prefix . 'ому',
158 2
                        self::VINIT => $prefix . 'ый',
159 2
                        self::TVORIT => $prefix . 'ым',
160 2
                        self::PREDLOJ => $prefix . 'ом',
161
                    ];
162
163
                // Ставрополь, Ярославль
164 24
                case 'ль':
165 1
                    $prefix = S::name(S::slice($name, 0, -1));
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 84 can also be of type false; however, morphos\S::slice() 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...
166
                    return [
167 1
                        self::IMENIT => $prefix . 'ь',
168 1
                        self::RODIT => $prefix . 'я',
169 1
                        self::DAT => $prefix . 'ю',
170 1
                        self::VINIT => $prefix . 'ь',
171 1
                        self::TVORIT => $prefix . 'ем',
172 1
                        self::PREDLOJ => $prefix . 'е',
173
                    ];
174
175
                // Тверь, Анадырь
176 23
                case 'рь':
177 2
                    $prefix = S::name(S::slice($name, 0, -1));
178 2
                    $last_vowel = S::slice($prefix, -2, -1);
179
                    return [
180 2
                        self::IMENIT => $prefix . 'ь',
181 2
                        self::RODIT => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'я'),
182 2
                        self::DAT => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
183 2
                        self::VINIT => $prefix . 'ь',
184 2
                        self::TVORIT => $prefix . (self::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
185 2
                        self::PREDLOJ => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'е'),
186
                    ];
187
188
                // Березники, Ессентуки
189 21
                case 'ки':
190 2
                    $prefix = S::name(S::slice($name, 0, -1));
191
                    return [
192 2
                        self::IMENIT => $prefix . 'и',
193 2
                        self::RODIT => $name == 'луки' ? $prefix : $prefix . 'ов',
194 2
                        self::DAT => $prefix . 'ам',
195 2
                        self::VINIT => $prefix . 'и',
196 2
                        self::TVORIT => $prefix . 'ами',
197 2
                        self::PREDLOJ => $prefix . 'ах',
198
                    ];
199
200
                // Пермь, Кемь
201 20
                case 'мь':
202 1
                    $prefix = S::name(S::slice($name, 0, -1));
203
                    return [
204 1
                        self::IMENIT => $prefix . 'ь',
205 1
                        self::RODIT => $prefix . 'и',
206 1
                        self::DAT => $prefix . 'и',
207 1
                        self::VINIT => $prefix . 'ь',
208 1
                        self::TVORIT => $prefix . 'ью',
209 1
                        self::PREDLOJ => $prefix . 'и',
210
                    ];
211
212
                // Рязань, Назрань
213 19
                case 'нь':
214 1
                    $prefix = S::name(S::slice($name, 0, -1));
215
                    return [
216 1
                        self::IMENIT => $prefix . 'ь',
217 1
                        self::RODIT => $prefix . 'и',
218 1
                        self::DAT => $prefix . 'и',
219 1
                        self::VINIT => $prefix . 'ь',
220 1
                        self::TVORIT => $prefix . 'ью',
221 1
                        self::PREDLOJ => $prefix . 'и',
222
                    ];
223
224
                // Набережные
225 18
                case 'ые':
226 1
                    $prefix = S::name(S::slice($name, 0, -1));
227
                    return [
228 1
                        self::IMENIT => $prefix . 'е',
229 1
                        self::RODIT => $prefix . 'х',
230 1
                        self::DAT => $prefix . 'м',
231 1
                        self::VINIT => $prefix . 'е',
232 1
                        self::TVORIT => $prefix . 'ми',
233 1
                        self::PREDLOJ => $prefix . 'х',
234
                    ];
235
236
                // Челны
237 18
                case 'ны':
238 1
                    $prefix = S::name(S::slice($name, 0, -1));
239
                    return [
240 1
                        self::IMENIT => $prefix . 'ы',
241 1
                        self::RODIT => $prefix . 'ов',
242 1
                        self::DAT => $prefix . 'ам',
243 1
                        self::VINIT => $prefix . 'ы',
244 1
                        self::TVORIT => $prefix . 'ами',
245 1
                        self::PREDLOJ => $prefix . 'ах',
246
                    ];
247
248
                // Великие
249 17
                case 'ие':
250 1
                    $prefix = S::name(S::slice($name, 0, -1));
251
                    return [
252 1
                        self::IMENIT => $prefix.'е',
253 1
                        self::RODIT => $prefix.'х',
254 1
                        self::DAT => $prefix.'м',
255 1
                        self::VINIT => $prefix.'е',
256 1
                        self::TVORIT => $prefix.'ми',
257 1
                        self::PREDLOJ => $prefix.'х',
258
                    ];
259
260
                // Керчь
261 16
                case 'чь':
262 1
                    $prefix = S::name(S::slice($name, 0, -1));
263
                    return [
264 1
                        self::IMENIT => $prefix.'ь',
265 1
                        self::RODIT => $prefix.'и',
266 1
                        self::DAT => $prefix.'и',
267 1
                        self::VINIT => $prefix.'ь',
268 1
                        self::TVORIT => $prefix.'ью',
269 1
                        self::PREDLOJ => $prefix.'и',
270
                    ];
271
            }
272
273
274 15
            switch (S::slice($name, -1)) {
275
                // Азия
276 15
                case 'я':
277 1
                    $prefix = S::name(S::slice($name, 0, -1));
278
                    return [
279 1
                        self::IMENIT => S::name($name),
280 1
                        self::RODIT => $prefix.'и',
281 1
                        self::DAT => $prefix.'и',
282 1
                        self::VINIT => $prefix.'ю',
283 1
                        self::TVORIT => $prefix.'ей',
284 1
                        self::PREDLOJ => $prefix.'и',
285
                    ];
286
287 14 View Code Duplication
                case 'а':
288
                    // Москва, Рига
289 5
                    $prefix = S::name(S::slice($name, 0, -1));
290
                    return [
291 5
                        self::IMENIT => $prefix.'а',
292 5
                        self::RODIT => $prefix.(self::isVelarConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 84 can also be of type false; however, morphos\S::slice() 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...
293 5
                        self::DAT => $prefix.'е',
294 5
                        self::VINIT => $prefix.'у',
295 5
                        self::TVORIT => $prefix.'ой',
296 5
                        self::PREDLOJ => $prefix.'е',
297
                    ];
298
299 9
                case 'й':
300
                    // Ишимбай
301 2
                    $prefix = S::name(S::slice($name, 0, -1));
302
                    return [
303 2
                        self::IMENIT => $prefix . 'й',
304 2
                        self::RODIT => $prefix . 'я',
305 2
                        self::DAT => $prefix . 'ю',
306 2
                        self::VINIT => $prefix . 'й',
307 2
                        self::TVORIT => $prefix . 'ем',
308 2
                        self::PREDLOJ => $prefix . 'е',
309
                    ];
310
            }
311
312 7
            if (self::isConsonant(S::slice($name,  -1)) && !in_array($name, self::$ovAbnormalExceptions, true)) {
313
                // Париж, Валаам, Киев
314 6
                $prefix = S::name($name);
315
                return [
316 6
                    self::IMENIT => $prefix,
317 6
                    self::RODIT => $prefix . 'а',
318 6
                    self::DAT => $prefix . 'у',
319 6
                    self::VINIT => $prefix,
320 6
                    self::TVORIT => $prefix . (self::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
321 6
                    self::PREDLOJ => $prefix . 'е',
322
                ];
323
            }
324
325
            // ов, ово, ёв, ёво, ев, ево, ...
326 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
327 2
            if ((in_array(S::slice($name, -1), ['е', 'о'], true) && in_array(S::slice($name, -3, -1), $suffixes, true)) || in_array(S::slice($name, -2), $suffixes, true)) {
328
                // ово, ёво, ...
329 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
330
                    $prefix = S::name(S::slice($name, 0, -1));
331
                }
332
                // ов, её, ...
333 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
334 1
                    $prefix = S::name($name);
335
                }
336
                return [
337 1
                    self::IMENIT => S::name($name),
338 1
                    self::RODIT => $prefix.'а',
339 1
                    self::DAT => $prefix.'у',
340 1
                    self::VINIT => S::name($name),
341 1
                    self::TVORIT => $prefix.'ым',
342 1
                    self::PREDLOJ => $prefix.'е',
343
                ];
344
            }
345
        }
346
347
        // if no rules matches or name is immutable
348 3
        $name = in_array($name, self::$abbreviations, true) ? S::upper($name) : S::name($name);
349 3
        return array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], $name);
350
    }
351
352
    /**
353
     * Получение одной формы (падежа) названия.
354
     * @param string $name  Название
355
     * @param integer $case Падеж. Одна из констант \morphos\Russian\Cases или \morphos\Cases.
356
     * @see \morphos\Russian\Cases
357
     * @return string
358
     * @throws \Exception
359
     */
360
    public static function getCase($name, $case)
361
    {
362
        $case = self::canonizeCase($case);
363
        $forms = self::getCases($name);
364
        return $forms[$case];
365
    }
366
}
367