GeographicalNamesInflection   F
last analyzed

Complexity

Total Complexity 81

Size/Duplication

Total Lines 583
Duplicated Lines 1.37 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 85.82%

Importance

Changes 0
Metric Value
dl 8
loc 583
ccs 242
cts 282
cp 0.8582
rs 2
c 0
b 0
f 0
wmc 81
lcom 1
cbo 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getRunAwayVowelsList() 8 8 2
B isMutable() 0 41 11
F getCases() 0 392 67
A getCase() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like GeographicalNamesInflection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GeographicalNamesInflection, and based on these observations, apply Extract Interface, too.

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
    /** @var bool Настройка склоняемости славянских топонимов на -ов(о), -ев(о), -ин(о), -ын(о) */
14
    public static $inflectSlavicNames = true;
15
16
    /** @var string[]  */
17
    protected static $abbreviations = [
18
        'сша',
19
        'оаэ',
20
        'ссср',
21
        'юар',
22
    ];
23
24
    /** @var string[]  */
25
    protected static $delimiters = [
26
        ' ',
27
        '-на-',
28
        '-эль-',
29
        '-де-',
30
        '-сюр-',
31
        '-ан-',
32
        '-ла-',
33
        '-',
34
    ];
35
36
    /** @var string[]  */
37
    protected static $ovAbnormalExceptions = [
38
        'осташков',
39
    ];
40
41
    /**
42
     * @var string[] Immutable names or name parts
43
     */
44
    protected static $immutableNames = [
45
        'алматы',
46
        'сочи',
47
        'гоа',
48
        'домодедово',
49
        'внуково',
50
        'шереметьево',
51
        'остафьево',
52
        'пулково',
53
54
        // фикс для Марий Эл
55
        'марий',
56
        'эл',
57
58
        // части
59
        'алма',
60
        'буда',
61
        'йошкар',
62
        'кабардино',
63
        'карачаево',
64
        'рублёво',
65
        'санкт',
66
        'улан',
67
        'ханты',
68
        'орехово',
69
        'лосино',
70
        'юрьево',
71
        'наро',
72
73
        // Зарубежные названия
74
        'пунта',
75
        'куала',
76
        'рас',
77
        'шарм',
78
        'гран',
79
        'гранд',
80
        'вильфранш',
81
        'льорет',
82
        'андорра',
83
        'экс',
84
        'эс',
85
        'сен',
86
        'ла',
87
    ];
88
89
    /** @var string[] */
90
    protected static $immutableTriggerPrefixes = [
91
        'спас',
92
        'усть',
93
        'соль',
94
    ];
95
96
    /** @var string[]  */
97
    protected static $runawayVowelsExceptions = [
98
        'торжо*к',
99
        'волоче*к',
100
        'орё*л',
101
        'египе*т',
102
        'лунине*ц',
103
        'городо*к',
104
        'новогрудо*к',
105
        'острове*ц',
106
        'черепове*ц',
107
    ];
108
109
    /**
110
     * @var string[]
111
     * @phpstan-var array<string, string>
112
     */
113
    protected static $misspellings = [
114
        'орел' => 'орёл',
115
        'рублево' => 'рублёво',
116
    ];
117
118
    /**
119
     * @return int[]|false[]
120
     */
121 13 View Code Duplication
    protected static function getRunAwayVowelsList()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
122
    {
123 13
        $runawayVowelsNormalized = [];
124 13
        foreach (static::$runawayVowelsExceptions as $word) {
125 13
            $runawayVowelsNormalized[str_replace('*', '', $word)] = S::indexOf($word, '*') - 1;
126
        }
127 13
        return $runawayVowelsNormalized;
128
    }
129
130
    /**
131
     * Проверяет, склоняемо ли название
132
     * @param string $name Название
133
     * @return bool
134
     */
135 2
    public static function isMutable($name)
136
    {
137 2
        $name = S::lower($name);
138
139
        // // ends with 'ы' or 'и': plural form
140
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
141
        //     return false;
142
143 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableNames, true)) {
144 2
            return false;
145
        }
146
147
        if (strpos($name, ' ') !== false) {
148
            // explode() is not applicable because Geographical unit may have few words
149
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
0 ignored issues
show
Security Bug introduced by
It seems like \morphos\S::findFirstPosition($name, ' ') targeting morphos\S::findFirstPosition() can also be of type false; however, morphos\S::slice() does only seem to accept integer|null, did you maybe forget to handle an error condition?
Loading history...
150
            $last_part = S::slice($name,
151
                S::findLastPosition($name, ' ') + 1);
152
153
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
154
            // N область, N край, N район, N волость
155
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
156
                || in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
157
                return true;
158
            }
159
160
            // пгт N
161
            if ($first_part === 'пгт')
162
                return false;
163
        }
164
165
        if (strpos($name, '-') !== false && S::stringContains($name, static::$immutableTriggerPrefixes)) {
166
            return false;
167
        }
168
169
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
170
        if (in_array(S::slice($name, -1), ['е', 'о'], true)
171
            && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
172
            return (bool)static::$inflectSlavicNames;
173
        }
174
        return true;
175
    }
176
177
    /**
178
     * Получение всех форм названия
179
     * @param string $name
180
     * @return string[]
181
     * @phpstan-return array<string, string>
182
     * @throws \Exception
183
     */
184 48
    public static function getCases($name)
185
    {
186 48
        $name = S::lower($name);
187
188
        // Проверка на неизменяемость
189 48
        if (in_array($name, static::$immutableNames, true)
190 48
            || (strpos($name, '-') !== false && S::stringContains($name, static::$immutableTriggerPrefixes))
191
        ) {
192 2
            return array_fill_keys(
193 2
                [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE]
194 2
                , S::name($name));
195
        }
196
197
        // Проверка на сложное название
198 48
        if (strpos($name, ' ') !== false) {
199 9
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
0 ignored issues
show
Security Bug introduced by
It seems like \morphos\S::findFirstPosition($name, ' ') targeting morphos\S::findFirstPosition() can also be of type false; however, morphos\S::slice() does only seem to accept integer|null, did you maybe forget to handle an error condition?
Loading history...
200
            // город N, село N, хутор N, пгт N
201 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
202 3
                if ($first_part === 'пгт')
203
                    return array_fill_keys(
204
                        [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
205
                        'пгт '.S::name(S::slice($name, 4)));
206
207 3
                if ($first_part === 'республика') {
208 1
                    $prefix = array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part));
209
                } else {
210 2
                    $prefix = NounDeclension::getCases($first_part);
211
                }
212 3
                $prefix[Cases::LOCATIVE] = $prefix[Cases::PREDLOJ];
213
214 3
                return static::composeCasesFromWords([$prefix,
215 3
                    array_fill_keys(
216 3
                        array_merge(static::getAllCases(), [\morphos\Russian\Cases::LOCATIVE]),
217 3
                        S::name(S::slice($name, S::length($first_part) + 1)))
218
                ]);
219
            }
220
221 6
            $last_part = S::slice($name,
222 6
                S::findLastPosition($name, ' ') + 1);
223
            // N область, N край
224 6
            if (in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
225 2
                $last_part_cases = NounDeclension::getCases($last_part);
226 2
                $last_part_cases[Cases::LOCATIVE] = $last_part_cases[Cases::PREDLOJ];
227 2
                return static::composeCasesFromWords(
228
                    [
229 2
                        static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))),
0 ignored issues
show
Security Bug introduced by
It seems like \morphos\S::findLastPosition($name, ' ') targeting morphos\S::findLastPosition() can also be of type false; however, morphos\S::slice() does only seem to accept integer|null, did you maybe forget to handle an error condition?
Loading history...
230 2
                        $last_part_cases,
231
                    ]);
232
            }
233
        }
234
235
        // Сложное название с разделителем
236 45
        foreach (static::$delimiters as $delimiter) {
237 45
            if (strpos($name, $delimiter) !== false) {
238 7
                $parts = explode($delimiter, $name);
239 7
                $result = [];
240 7
                foreach ($parts as $i => $part) {
241 7
                    $result[$i] = static::getCases($part);
242
                }
243 45
                return static::composeCasesFromWords($result, $delimiter);
244
            }
245
        }
246
247
        // Исправление ошибок
248 45
        if (array_key_exists($name, static::$misspellings)) {
249
            $name = static::$misspellings[$name];
250
        }
251
252
        // Само склонение
253 45
        if (!in_array($name, static::$abbreviations, true)) {
254 43
            switch (S::slice($name, -2)) {
255
                // Нижний, Русский
256 43
                case 'ий':
257 3
                    $prefix = S::name(S::slice($name, 0, -2));
258
                    return [
259 3
                        static::IMENIT => $prefix.'ий',
260 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
261 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
262 3
                        static::VINIT => $prefix.'ий',
263 3
                        static::TVORIT => $prefix.'им',
264 3
                        static::PREDLOJ => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
0 ignored issues
show
Bug introduced by
It seems like $prefix defined by \morphos\S::name(\morphos\S::slice($name, 0, -2)) on line 257 can also be of type boolean; however, morphos\Russian\RussianL...hooseEndingBySonority() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
265 3
                        static::LOCATIVE => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
0 ignored issues
show
Bug introduced by
It seems like $prefix defined by \morphos\S::name(\morphos\S::slice($name, 0, -2)) on line 257 can also be of type boolean; however, morphos\Russian\RussianL...hooseEndingBySonority() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
266
                    ];
267
268
                // Ростовская
269 42
                case 'ая':
270 1
                    $prefix = S::name(S::slice($name, 0, -2));
271
                    return [
272 1
                        static::IMENIT => $prefix.'ая',
273 1
                        static::RODIT => $prefix.'ой',
274 1
                        static::DAT => $prefix.'ой',
275 1
                        static::VINIT => $prefix.'ую',
276 1
                        static::TVORIT => $prefix.'ой',
277 1
                        static::PREDLOJ => $prefix.'ой',
278 1
                        static::LOCATIVE => $prefix.'ой',
279
                    ];
280
281
                // Нижняя, Верхняя, Средняя
282 41
                case 'яя':
283
                    $prefix = S::name(S::slice($name, 0, -2));
284
                    return [
285
                        static::IMENIT => $prefix.'яя',
286
                        static::RODIT => $prefix.'ей',
287
                        static::DAT => $prefix.'ей',
288
                        static::VINIT => $prefix.'юю',
289
                        static::TVORIT => $prefix.'ей',
290
                        static::PREDLOJ => $prefix.'ей',
291
                        static::LOCATIVE => $prefix.'ей',
292
                    ];
293
294
                // Россошь
295 41
                case 'шь':
296
                // Пермь, Кемь
297 40
                case 'мь':
298
                // Рязань, Назрань
299 39
                case 'нь':
300
                // Сысерть
301 38
                case 'ть':
302
                // Керчь
303 38
                case 'чь':
304 4
                    $prefix = S::name(S::slice($name, 0, -1));
305
                    return [
306 4
                        static::IMENIT => $prefix.'ь',
307 4
                        static::RODIT => $prefix.'и',
308 4
                        static::DAT => $prefix.'и',
309 4
                        static::VINIT => $prefix.'ь',
310 4
                        static::TVORIT => $prefix.'ью',
311 4
                        static::PREDLOJ => $prefix.'и',
312 4
                        static::LOCATIVE => $prefix.'и',
313
                    ];
314
315
                // Грозный, Благодарный
316 37
                case 'ый':
317 2
                    $prefix = S::name(S::slice($name, 0, -2));
318
                    return [
319 2
                        static::IMENIT => $prefix.'ый',
320 2
                        static::RODIT => $prefix.'ого',
321 2
                        static::DAT => $prefix.'ому',
322 2
                        static::VINIT => $prefix.'ый',
323 2
                        static::TVORIT => $prefix.'ым',
324 2
                        static::PREDLOJ => $prefix.'ом',
325 2
                        static::LOCATIVE => $prefix.'ом',
326
                    ];
327
328
                // Ставрополь, Ярославль, Электросталь
329 35
                case 'ль':
330 2
                    $prefix = S::name(S::slice($name, 0, -1));
331
332 2
                    if ($name === 'электросталь')
333
                        return [
334 1
                            static::IMENIT => $prefix.'ь',
335 1
                            static::RODIT => $prefix.'и',
336 1
                            static::DAT => $prefix.'и',
337 1
                            static::VINIT => $prefix.'ь',
338 1
                            static::TVORIT => $prefix.'ью',
339 1
                            static::PREDLOJ => $prefix.'и',
340 1
                            static::LOCATIVE => $prefix.'и',
341
                        ];
342
343
                    return [
344 1
                        static::IMENIT => $prefix.'ь',
345 1
                        static::RODIT => $prefix.'я',
346 1
                        static::DAT => $prefix.'ю',
347 1
                        static::VINIT => $prefix.'ь',
348 1
                        static::TVORIT => $prefix.'ем',
349 1
                        static::PREDLOJ => $prefix.'е',
350 1
                        static::LOCATIVE => $prefix.'е',
351
                    ];
352
353
                // Адыгея, Чечня
354 33
                case 'ея':
355 32
                case 'ня':
356 1
                    $prefix = S::name(S::slice($name, 0, -1));
357
                    return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME...IVE => $prefix . 'е'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
358 1
                        static::IMENIT => S::name($name),
359 1
                        static::RODIT => $prefix.'е',
360 1
                        static::DAT => $prefix.'е',
361 1
                        static::VINIT => $prefix.'я',
362 1
                        static::TVORIT => $prefix.'ей',
363 1
                        static::PREDLOJ => $prefix.'е',
364 1
                        static::LOCATIVE => $prefix.'е',
365
                    ];
366
367
                // Тверь, Анадырь
368 32
                case 'рь':
369 2
                    $prefix = S::name(S::slice($name, 0, -1));
370 2
                    $last_vowel = S::slice($prefix, -2, -1);
0 ignored issues
show
Bug introduced by
It seems like $prefix defined by \morphos\S::name(\morphos\S::slice($name, 0, -1)) on line 369 can also be of type boolean; however, morphos\S::slice() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
371
                    return [
372 2
                        static::IMENIT => $prefix . 'ь',
373 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
374 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
375 2
                        static::VINIT => $prefix . 'ь',
376 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
377 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
378 2
                        static::LOCATIVE => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
379
                    ];
380
381
                // Березники, Ессентуки
382 30
                case 'ки':
383
                // Старые Дороги
384 29
                case 'ги':
385
                // Ушачи, Ивацевичи
386 29
                case 'чи':
387 3
                    $prefix = S::name(S::slice($name, 0, -1));
388
                    return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME...E => $prefix . 'ах'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
389 3
                        static::IMENIT => $prefix . 'и',
390 3
                        static::RODIT => ($name === 'луки'
391 1
                            ? $prefix
392 2
                            : (S::slice($name, -2) === 'чи'
393 1
                                ? $prefix . 'ей'
394 3
                                : $prefix . 'ов')),
395 3
                        static::DAT => $prefix . 'ам',
396 3
                        static::VINIT => $prefix . 'и',
397 3
                        static::TVORIT => $prefix . 'ами',
398 3
                        static::PREDLOJ => $prefix . 'ах',
399 3
                        static::LOCATIVE => $prefix . 'ах',
400
                    ];
401
402
                // Набережные
403 28
                case 'ые':
404
                // Великие
405 28
                case 'ие':
406 2
                    $prefix = S::name(S::slice($name, 0, -1));
407
                    return [
408 2
                        static::IMENIT => $prefix . 'е',
409 2
                        static::RODIT => $prefix . 'х',
410 2
                        static::DAT => $prefix . 'м',
411 2
                        static::VINIT => $prefix . 'е',
412 2
                        static::TVORIT => $prefix . 'ми',
413 2
                        static::PREDLOJ => $prefix . 'х',
414 2
                        static::LOCATIVE => $prefix . 'х',
415
                    ];
416
417
                // Челны
418 27
                case 'ны':
419
                // Мосты
420 26
                case 'ты':
421
                // Столбцы
422 26
                case 'цы':
423 2
                    $prefix = S::name(S::slice($name, 0, -1));
424
                    return [
425 2
                        static::IMENIT => $prefix . 'ы',
426 2
                        static::RODIT => $prefix . 'ов',
427 2
                        static::DAT => $prefix . 'ам',
428 2
                        static::VINIT => $prefix . 'ы',
429 2
                        static::TVORIT => $prefix . 'ами',
430 2
                        static::PREDLOJ => $prefix . 'ах',
431 2
                        static::LOCATIVE => $prefix . 'ах',
432
                    ];
433
434
                // Глубокое
435 25
                case 'ое':
436 1
                    $prefix = S::name(S::slice($name, 0, -2));
437
                    return [
438 1
                        static::IMENIT => $prefix.'ое',
439 1
                        static::RODIT => $prefix.'ого',
440 1
                        static::DAT => $prefix.'ому',
441 1
                        static::VINIT => $prefix.'ое',
442 1
                        static::TVORIT => $prefix.'им',
443 1
                        static::PREDLOJ => $prefix.'ом',
444 1
                        static::LOCATIVE => $prefix.'ом',
445
                    ];
446
            }
447
448 24
            switch (S::slice($name, -1)) {
449 24
                case 'р':
450
                    // Бор
451
                    $prefix = S::name(S::slice($name, 0, -1));
452
                    return [
453
                        static::IMENIT => $prefix.'р',
454
                        static::RODIT => $prefix.'ра',
455
                        static::DAT => $prefix.'ру',
456
                        static::VINIT => $prefix.'р',
457
                        static::TVORIT => $prefix.'ром',
458
                        static::PREDLOJ => $prefix.'ре',
459
                        static::LOCATIVE => $prefix.'ру',
460
                    ];
461
462 24
                case 'ы':
463
                    // Чебоксары, Шахты
464 1
                    $prefix = S::name(S::slice($name, 0, -1));
465
                    return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME...E => $prefix . 'ах'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
466 1
                        static::IMENIT => $prefix.'ы',
467 1
                        static::RODIT => $prefix,
468 1
                        static::DAT => $prefix.'ам',
469 1
                        static::VINIT => $prefix.'ы',
470 1
                        static::TVORIT => $prefix.'ами',
471 1
                        static::PREDLOJ => $prefix.'ах',
472 1
                        static::LOCATIVE => $prefix.'ах',
473
                    ];
474
475 23
                case 'я':
476
                    // Азия
477 1
                    $prefix = S::name(S::slice($name, 0, -1));
478
                    return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME...IVE => $prefix . 'и'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
479 1
                        static::IMENIT => S::name($name),
480 1
                        static::RODIT => $prefix.'и',
481 1
                        static::DAT => $prefix.'и',
482 1
                        static::VINIT => $prefix.'ю',
483 1
                        static::TVORIT => $prefix.'ей',
484 1
                        static::PREDLOJ => $prefix.'и',
485 1
                        static::LOCATIVE => $prefix.'и',
486
                    ];
487
488 22
                case 'а':
489
                    // Москва, Рига
490 5
                    $prefix = S::name(S::slice($name, 0, -1));
491
                    return [
492 5
                        static::IMENIT => $prefix.'а',
493 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) || static::isHissingConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
494 5
                        static::DAT => $prefix.'е',
495 5
                        static::VINIT => $prefix.'у',
496 5
                        static::TVORIT => $prefix.'ой',
497 5
                        static::PREDLOJ => $prefix.'е',
498 5
                        static::LOCATIVE => $prefix.'е',
499
                    ];
500
501 17
                case 'й':
502
                    // Ишимбай
503 2
                    $prefix = S::name(S::slice($name, 0, -1));
504
                    return [
505 2
                        static::IMENIT => $prefix . 'й',
506 2
                        static::RODIT => $prefix . 'я',
507 2
                        static::DAT => $prefix . 'ю',
508 2
                        static::VINIT => $prefix . 'й',
509 2
                        static::TVORIT => $prefix . 'ем',
510 2
                        static::PREDLOJ => $prefix . 'е',
511 2
                        static::LOCATIVE => $prefix . 'е',
512
                    ];
513
            }
514
515 15
            if (static::isConsonant($last_char = S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
516 13
                $runaway_vowels_list = static::getRunAwayVowelsList();
517
518
                // if run-away vowel in name
519 13
                if (isset($runaway_vowels_list[$name])) {
520 4
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
521 4
                    $prefix = S::name(S::slice($name, 0, $runaway_vowel_offset) . S::slice($name, $runaway_vowel_offset + 1));
0 ignored issues
show
Security Bug introduced by
It seems like $runaway_vowel_offset defined by $runaway_vowels_list[$name] on line 520 can also be of type false; however, morphos\S::slice() does only seem to accept integer|null, 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...
522
                } else {
523 9
                    $prefix = S::name($name);
524
                }
525
526
                // Париж, Валаам, Киев
527
                return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME... seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
528 13
                    static::IMENIT => S::name($name),
529 13
                    static::RODIT => $prefix . 'а',
530 13
                    static::DAT => $prefix . 'у',
531 13
                    static::VINIT => S::name($name),
532 13
                    static::TVORIT => $prefix . (
533 13
                        static::isVelarConsonant(S::slice($name, -2, -1))
534 13
                            || static::isHissingConsonant($last_char)
535 13
                        ? 'ем' : 'ом'),
536 13
                    static::PREDLOJ => $prefix . 'е',
537 13
                    static::LOCATIVE => $prefix.($name === 'крым' ? 'у' : 'е'),
538
                ];
539
            }
540
541
            // ов, ово, ёв, ёво, ев, ево, ...
542 3
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
543 3
            if (static::$inflectSlavicNames && (
544 3
                (in_array(S::slice($name, -1), ['е', 'о'], true) && in_array(S::slice($name, -3, -1), $suffixes, true))
545 3
                || in_array(S::slice($name, -2), $suffixes, true)
546
                )) {
547
                // ово, ёво, ...
548 2
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
549 1
                    $prefix = S::name(S::slice($name, 0, -1));
550
                }
551
                // ов, её, ...
552 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
553 1
                    $prefix = S::name($name);
554
                } else {
555
                    $prefix = '';
556
                }
557
558
                return [
0 ignored issues
show
Best Practice introduced by
The expression return array(static::IME...IVE => $prefix . 'е'); seems to be an array, but some of its elements' types (boolean) are incompatible with the return type documented by morphos\Russian\Geograph...mesInflection::getCases of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
559 2
                    static::IMENIT => S::name($name),
560 2
                    static::RODIT => $prefix.'а',
561 2
                    static::DAT => $prefix.'у',
562 2
                    static::VINIT => S::name($name),
563 2
                    static::TVORIT => $name !== 'осташков' ? $prefix.'ом' : $prefix.'ым',
564 2
                    static::PREDLOJ => $prefix.'е',
565 2
                    static::LOCATIVE => $prefix.'е',
566
                ];
567
            }
568
        }
569
570
        // if no rules matches or name is immutable
571 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
572 3
        return array_fill_keys(
573 3
            [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
574 3
            $name);
575
    }
576
577
    /**
578
     * Получение одной формы (падежа) названия.
579
     * @param string $name  Название
580
     * @param string $case Падеж. Одна из констант {@see \morphos\Russian\Cases} или {@see \morphos\Cases}.
581
     * @see \morphos\Russian\Cases
582
     * @return string
583
     * @throws \Exception
584
     */
585
    public static function getCase($name, $case)
586
    {
587
        $case = static::canonizeCase($case);
588
        $forms = static::getCases($name);
589
        return $forms[$case];
590
    }
591
}
592