Completed
Push — master ( 843310...3e9521 )
by f
01:50
created

src/Russian/GeographicalNamesInflection.php (13 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
        // N край
52
        if (S::slice($name, -5) == ' край') {
53
            return static::isMutable(S::slice($name, 0, -5));
54
        }
55
56
        // N область
57
        if (S::slice($name, -8) == ' область') {
58
            return true;
59
        }
60
61
        // город N
62
        if (S::slice($name, 0, 6) == 'город ') {
63
            return true;
64
        }
65
66
        // село N
67
        if (S::slice($name, 0, 5) == 'село ') {
68
            return true;
69
        }
70
71
        // хутор N
72
        if (S::slice($name, 0, 6) == 'хутор ') {
73
            return true;
74
        }
75
76
        // пгт N
77
        if (S::slice($name, 0, 4) == 'пгт ') {
78
            return false;
79
        }
80
81
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
82
        if (in_array(S::slice($name, -1), ['е', 'о'], true) && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
83
            return false;
84
        }
85
        return true;
86
    }
87
88
    /**
89
     * Получение всех форм названия
90
     * @param string $name
91
     * @return array
92
     * @throws \Exception
93
     */
94 32
    public static function getCases($name)
95
    {
96 32
        $name = S::lower($name);
97
98 32
        if (in_array($name, self::$immutableParts, true)) {
99 1
            return array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], S::name($name));
100
        }
101
102
        // N край
103 32 View Code Duplication
        if (S::slice($name, -5) == ' край') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
104 1
            return self::composeCasesFromWords([static::getCases(S::slice($name, 0, -5)), NounDeclension::getCases('край')]);
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
It seems like \morphos\S::slice($name, 0, -5) targeting morphos\S::slice() can also be of type false; however, morphos\Russian\Geograph...sInflection::getCases() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
105
        }
106
107
        // N область
108 32 View Code Duplication
        if (S::slice($name, -8) == ' область') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
109 1
            return self::composeCasesFromWords([static::getCases(S::slice($name, 0, -8)), NounDeclension::getCases('область')]);
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
It seems like \morphos\S::slice($name, 0, -8) targeting morphos\S::slice() can also be of type false; however, morphos\Russian\Geograph...sInflection::getCases() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
110
        }
111
112
        // город N
113 32 View Code Duplication
        if (S::slice($name, 0, 6) == 'город ') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
114 1
            return self::composeCasesFromWords([
115 1
                NounDeclension::getCases('город'),
116 1
                array_fill_keys(self::getAllCases(), S::name(S::slice($name, 6)))
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
117
            ]);
118
        }
119
120
        // село N
121 31 View Code Duplication
        if (S::slice($name, 0, 5) == 'село ') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
122 1
            return self::composeCasesFromWords([
123 1
                NounDeclension::getCases('село'),
124 1
                array_fill_keys(self::getAllCases(), S::name(S::slice($name, 5)))
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
125
            ]);
126
        }
127
128
        // хутор N
129 30 View Code Duplication
        if (S::slice($name, 0, 6) == 'хутор ') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
130
            return self::composeCasesFromWords([
131
                NounDeclension::getCases('хутор'),
132
                array_fill_keys(self::getAllCases(), S::name(S::slice($name, 6)))
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
133
            ]);
134
        }
135
136
        // пгт N
137 30
        if (S::slice($name, 0, 4) == 'пгт ') {
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 96 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...
138
            return self::composeCasesFromWords([
139
                array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], 'пгт '.S::name(S::slice($name, 4)))
140
            ]);
141
        }
142
143
        // Сложное название через пробел, '-' или '-на-'
144 30
        foreach (self::$delimiters as $delimiter) {
145 30
            if (strpos($name, $delimiter) !== false) {
146 5
                $parts = explode($delimiter, $name);
147 5
                $result = [];
148 5
                foreach ($parts as $i => $part) {
149 5
                    $result[$i] = static::getCases($part);
150
                }
151 30
                return self::composeCasesFromWords($result, $delimiter);
152
            }
153
        }
154
155 30
        if (!in_array($name, self::$abbreviations, true)) {
156 28
            switch (S::slice($name, -2)) {
157
                // Нижний, Русский
158 28
                case 'ий':
159 2
                    $prefix = S::name(S::slice($name, 0, -2));
160
                    return [
161 2
                        self::IMENIT => $prefix . 'ий',
162 2
                        self::RODIT => $prefix . (self::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
163 2
                        self::DAT => $prefix . (self::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
164 2
                        self::VINIT => $prefix . 'ий',
165 2
                        self::TVORIT => $prefix . 'им',
166 2
                        self::PREDLOJ => $prefix . (self::chooseEndingBySonority($prefix, 'ем', 'ом')),
167
                    ];
168
169
                // Ростовская
170 27
                case 'ая':
171 1
                    $prefix = S::name(S::slice($name, 0, -2));
172
                    return [
173 1
                        self::IMENIT => $prefix . 'ая',
174 1
                        self::RODIT => $prefix . 'ой',
175 1
                        self::DAT => $prefix . 'ой',
176 1
                        self::VINIT => $prefix . 'ую',
177 1
                        self::TVORIT => $prefix . 'ой',
178 1
                        self::PREDLOJ => $prefix . 'ой',
179
                    ];
180
181
                // Грозный, Благодарный
182 26
                case 'ый':
183 2
                    $prefix = S::name(S::slice($name, 0, -2));
184
                    return [
185 2
                        self::IMENIT => $prefix . 'ый',
186 2
                        self::RODIT => $prefix . 'ого',
187 2
                        self::DAT => $prefix . 'ому',
188 2
                        self::VINIT => $prefix . 'ый',
189 2
                        self::TVORIT => $prefix . 'ым',
190 2
                        self::PREDLOJ => $prefix . 'ом',
191
                    ];
192
193
                // Ставрополь, Ярославль
194 24
                case 'ль':
195 1
                    $prefix = S::name(S::slice($name, 0, -1));
196
                    return [
197 1
                        self::IMENIT => $prefix . 'ь',
198 1
                        self::RODIT => $prefix . 'я',
199 1
                        self::DAT => $prefix . 'ю',
200 1
                        self::VINIT => $prefix . 'ь',
201 1
                        self::TVORIT => $prefix . 'ем',
202 1
                        self::PREDLOJ => $prefix . 'е',
203
                    ];
204
205
                // Тверь, Анадырь
206 23
                case 'рь':
207 2
                    $prefix = S::name(S::slice($name, 0, -1));
208 2
                    $last_vowel = S::slice($prefix, -2, -1);
209
                    return [
210 2
                        self::IMENIT => $prefix . 'ь',
211 2
                        self::RODIT => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'я'),
212 2
                        self::DAT => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
213 2
                        self::VINIT => $prefix . 'ь',
214 2
                        self::TVORIT => $prefix . (self::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
215 2
                        self::PREDLOJ => $prefix . (self::isBinaryVowel($last_vowel) ? 'и' : 'е'),
216
                    ];
217
218
                // Березники, Ессентуки
219 21
                case 'ки':
220 2
                    $prefix = S::name(S::slice($name, 0, -1));
221
                    return [
222 2
                        self::IMENIT => $prefix . 'и',
223 2
                        self::RODIT => $name == 'луки' ? $prefix : $prefix . 'ов',
224 2
                        self::DAT => $prefix . 'ам',
225 2
                        self::VINIT => $prefix . 'и',
226 2
                        self::TVORIT => $prefix . 'ами',
227 2
                        self::PREDLOJ => $prefix . 'ах',
228
                    ];
229
230
                // Пермь, Кемь
231 20
                case 'мь':
232 1
                    $prefix = S::name(S::slice($name, 0, -1));
233
                    return [
234 1
                        self::IMENIT => $prefix . 'ь',
235 1
                        self::RODIT => $prefix . 'и',
236 1
                        self::DAT => $prefix . 'и',
237 1
                        self::VINIT => $prefix . 'ь',
238 1
                        self::TVORIT => $prefix . 'ью',
239 1
                        self::PREDLOJ => $prefix . 'и',
240
                    ];
241
242
                // Рязань, Назрань
243 19
                case 'нь':
244 1
                    $prefix = S::name(S::slice($name, 0, -1));
245
                    return [
246 1
                        self::IMENIT => $prefix . 'ь',
247 1
                        self::RODIT => $prefix . 'и',
248 1
                        self::DAT => $prefix . 'и',
249 1
                        self::VINIT => $prefix . 'ь',
250 1
                        self::TVORIT => $prefix . 'ью',
251 1
                        self::PREDLOJ => $prefix . 'и',
252
                    ];
253
254
                // Набережные
255 18
                case 'ые':
256 1
                    $prefix = S::name(S::slice($name, 0, -1));
257
                    return [
258 1
                        self::IMENIT => $prefix . 'е',
259 1
                        self::RODIT => $prefix . 'х',
260 1
                        self::DAT => $prefix . 'м',
261 1
                        self::VINIT => $prefix . 'е',
262 1
                        self::TVORIT => $prefix . 'ми',
263 1
                        self::PREDLOJ => $prefix . 'х',
264
                    ];
265
266
                // Челны
267 18
                case 'ны':
268 1
                    $prefix = S::name(S::slice($name, 0, -1));
269
                    return [
270 1
                        self::IMENIT => $prefix . 'ы',
271 1
                        self::RODIT => $prefix . 'ов',
272 1
                        self::DAT => $prefix . 'ам',
273 1
                        self::VINIT => $prefix . 'ы',
274 1
                        self::TVORIT => $prefix . 'ами',
275 1
                        self::PREDLOJ => $prefix . 'ах',
276
                    ];
277
278
                // Великие
279 17
                case 'ие':
280 1
                    $prefix = S::name(S::slice($name, 0, -1));
281
                    return [
282 1
                        self::IMENIT => $prefix.'е',
283 1
                        self::RODIT => $prefix.'х',
284 1
                        self::DAT => $prefix.'м',
285 1
                        self::VINIT => $prefix.'е',
286 1
                        self::TVORIT => $prefix.'ми',
287 1
                        self::PREDLOJ => $prefix.'х',
288
                    ];
289
290
                // Керчь
291 16
                case 'чь':
292 1
                    $prefix = S::name(S::slice($name, 0, -1));
293
                    return [
294 1
                        self::IMENIT => $prefix.'ь',
295 1
                        self::RODIT => $prefix.'и',
296 1
                        self::DAT => $prefix.'и',
297 1
                        self::VINIT => $prefix.'ь',
298 1
                        self::TVORIT => $prefix.'ью',
299 1
                        self::PREDLOJ => $prefix.'и',
300
                    ];
301
            }
302
303
304 15
            switch (S::slice($name, -1)) {
305
                // Азия
306 15
                case 'я':
307 1
                    $prefix = S::name(S::slice($name, 0, -1));
308
                    return [
309 1
                        self::IMENIT => S::name($name),
310 1
                        self::RODIT => $prefix.'и',
311 1
                        self::DAT => $prefix.'и',
312 1
                        self::VINIT => $prefix.'ю',
313 1
                        self::TVORIT => $prefix.'ей',
314 1
                        self::PREDLOJ => $prefix.'и',
315
                    ];
316
317 14 View Code Duplication
                case 'а':
318
                    // Москва, Рига
319 5
                    $prefix = S::name(S::slice($name, 0, -1));
320
                    return [
321 5
                        self::IMENIT => $prefix.'а',
322 5
                        self::RODIT => $prefix.(self::isVelarConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
323 5
                        self::DAT => $prefix.'е',
324 5
                        self::VINIT => $prefix.'у',
325 5
                        self::TVORIT => $prefix.'ой',
326 5
                        self::PREDLOJ => $prefix.'е',
327
                    ];
328
329 9
                case 'й':
330
                    // Ишимбай
331 2
                    $prefix = S::name(S::slice($name, 0, -1));
332
                    return [
333 2
                        self::IMENIT => $prefix . 'й',
334 2
                        self::RODIT => $prefix . 'я',
335 2
                        self::DAT => $prefix . 'ю',
336 2
                        self::VINIT => $prefix . 'й',
337 2
                        self::TVORIT => $prefix . 'ем',
338 2
                        self::PREDLOJ => $prefix . 'е',
339
                    ];
340
            }
341
342 7
            if (self::isConsonant(S::slice($name,  -1)) && !in_array($name, self::$ovAbnormalExceptions, true)) {
343
                // Париж, Валаам, Киев
344 6
                $prefix = S::name($name);
345
                return [
346 6
                    self::IMENIT => $prefix,
347 6
                    self::RODIT => $prefix . 'а',
348 6
                    self::DAT => $prefix . 'у',
349 6
                    self::VINIT => $prefix,
350 6
                    self::TVORIT => $prefix . (self::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
351 6
                    self::PREDLOJ => $prefix . 'е',
352
                ];
353
            }
354
355
            // ов, ово, ёв, ёво, ев, ево, ...
356 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
357 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)) {
358
                // ово, ёво, ...
359 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
360
                    $prefix = S::name(S::slice($name, 0, -1));
361
                }
362
                // ов, её, ...
363 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
364 1
                    $prefix = S::name($name);
365
                }
366
                return [
367 1
                    self::IMENIT => S::name($name),
368 1
                    self::RODIT => $prefix.'а',
369 1
                    self::DAT => $prefix.'у',
370 1
                    self::VINIT => S::name($name),
371 1
                    self::TVORIT => $prefix.'ым',
372 1
                    self::PREDLOJ => $prefix.'е',
373
                ];
374
            }
375
        }
376
377
        // if no rules matches or name is immutable
378 3
        $name = in_array($name, self::$abbreviations, true) ? S::upper($name) : S::name($name);
379 3
        return array_fill_keys([self::IMENIT, self::RODIT, self::DAT, self::VINIT, self::TVORIT, self::PREDLOJ], $name);
380
    }
381
382
    /**
383
     * Получение одной формы (падежа) названия.
384
     * @param string $name  Название
385
     * @param integer $case Падеж. Одна из констант \morphos\Russian\Cases или \morphos\Cases.
386
     * @see \morphos\Russian\Cases
387
     * @return string
388
     * @throws \Exception
389
     */
390
    public static function getCase($name, $case)
391
    {
392
        $case = self::canonizeCase($case);
393
        $forms = self::getCases($name);
394
        return $forms[$case];
395
    }
396
}
397