Completed
Push — master ( 5a8b5b...ba7f49 )
by f
13:41 queued 12s
created

GeographicalNamesInflection   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 532
Duplicated Lines 1.5 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 85.23%

Importance

Changes 0
Metric Value
dl 8
loc 532
ccs 225
cts 264
cp 0.8523
rs 2.64
c 0
b 0
f 0
wmc 72
lcom 1
cbo 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getRunAwayVowelsList() 8 8 2
B isMutable() 0 37 9
F getCases() 0 370 60
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 string[]  */
14
    protected static $abbreviations = [
15
        'сша',
16
        'оаэ',
17
        'ссср',
18
        'юар',
19
    ];
20
21
    /** @var string[]  */
22
    protected static $delimiters = [
23
        ' ',
24
        '-на-',
25
        '-эль-',
26
        '-де-',
27
        '-сюр-',
28
        '-ан-',
29
        '-ла-',
30
        '-',
31
    ];
32
33
    /** @var string[]  */
34
    protected static $ovAbnormalExceptions = [
35
        'осташков',
36
    ];
37
38
    /** @var string[]  */
39
    protected static $immutableNames = [
40
        'алматы',
41
        'сочи',
42
        'гоа',
43
        'кемерово',
44
        'назарово',
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
    /** @var string[]  */
72
    protected static $runawayVowelsExceptions = [
73
        'торжо*к',
74
        'волоче*к',
75
        'орё*л',
76
        'египе*т',
77
        'лунине*ц',
78
        'городо*к',
79
        'новогрудо*к',
80
        'острове*ц',
81
        'черепове*ц',
82
    ];
83
84
    /**
85
     * @var string[]
86
     * @phpstan-var array<string, string>
87
     */
88
    protected static $misspellings = [
89
        'орел' => 'орёл',
90
        'рублево' => 'рублёво',
91
    ];
92
93
    /**
94
     * @return int[]|false[]
95
     */
96 12 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...
97
    {
98 12
        $runawayVowelsNormalized = [];
99 12
        foreach (static::$runawayVowelsExceptions as $word) {
100 12
            $runawayVowelsNormalized[str_replace('*', '', $word)] = S::indexOf($word, '*') - 1;
101
        }
102 12
        return $runawayVowelsNormalized;
103
    }
104
105
    /**
106
     * Проверяет, склоняемо ли название
107
     * @param string $name Название
108
     * @return bool
109
     */
110 2
    public static function isMutable($name)
111
    {
112 2
        $name = S::lower($name);
113
114
        // // ends with 'ы' or 'и': plural form
115
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
116
        //     return false;
117
118 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableNames, true)) {
119 2
            return false;
120
        }
121
122
        if (strpos($name, ' ') !== false) {
123
            // explode() is not applicable because Geographical unit may have few words
124
            $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...
125
            $last_part = S::slice($name,
126
                S::findLastPosition($name, ' ') + 1);
127
128
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
129
            // N область, N край, N район, N волость
130
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
131
                || in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
132
                return true;
133
            }
134
135
            // пгт N
136
            if ($first_part === 'пгт')
137
                return false;
138
        }
139
140
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
141
        if (in_array(S::slice($name, -1), ['е', 'о'], true)
142
            && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
143
            return false;
144
        }
145
        return true;
146
    }
147
148
    /**
149
     * Получение всех форм названия
150
     * @param string $name
151
     * @return string[]
152
     * @phpstan-return array<string, string>
153
     * @throws \Exception
154
     */
155 45
    public static function getCases($name)
156
    {
157 45
        $name = S::lower($name);
158
159
        // Проверка на неизменяемость и сложное название
160 45
        if (in_array($name, static::$immutableNames, true)) {
161 2
            return array_fill_keys(
162 2
                [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE]
163 2
                , S::name($name));
164
        }
165
166 45
        if (strpos($name, ' ') !== false) {
167 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...
168
            // город N, село N, хутор N, пгт N
169 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
170 3
                if ($first_part === 'пгт')
171
                    return array_fill_keys(
172
                        [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
173
                        'пгт '.S::name(S::slice($name, 4)));
174
175 3
                if ($first_part === 'республика') {
176 1
                    $prefix = array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part));
177
                } else {
178 2
                    $prefix = NounDeclension::getCases($first_part);
179
                }
180 3
                $prefix[Cases::LOCATIVE] = $prefix[Cases::PREDLOJ];
181
182 3
                return static::composeCasesFromWords([$prefix,
183 3
                    array_fill_keys(
184 3
                        array_merge(static::getAllCases(), [\morphos\Russian\Cases::LOCATIVE]),
185 3
                        S::name(S::slice($name, S::length($first_part) + 1)))
186
                ]);
187
            }
188
189 6
            $last_part = S::slice($name,
190 6
                S::findLastPosition($name, ' ') + 1);
191
            // N область, N край
192 6
            if (in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
193 2
                $last_part_cases = NounDeclension::getCases($last_part);
194 2
                $last_part_cases[Cases::LOCATIVE] = $last_part_cases[Cases::PREDLOJ];
195 2
                return static::composeCasesFromWords(
196
                    [
197 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...
198 2
                        $last_part_cases,
199
                    ]);
200
            }
201
        }
202
203
        // Сложное название с разделителем
204 42
        foreach (static::$delimiters as $delimiter) {
205 42
            if (strpos($name, $delimiter) !== false) {
206 7
                $parts = explode($delimiter, $name);
207 7
                $result = [];
208 7
                foreach ($parts as $i => $part) {
209 7
                    $result[$i] = static::getCases($part);
210
                }
211 42
                return static::composeCasesFromWords($result, $delimiter);
212
            }
213
        }
214
215
        // Исправление ошибок
216 42
        if (array_key_exists($name, static::$misspellings)) {
217
            $name = static::$misspellings[$name];
218
        }
219
220
        // Само склонение
221 42
        if (!in_array($name, static::$abbreviations, true)) {
222 40
            switch (S::slice($name, -2)) {
223
                // Нижний, Русский
224 40
                case 'ий':
225 3
                    $prefix = S::name(S::slice($name, 0, -2));
226
                    return [
227 3
                        static::IMENIT => $prefix.'ий',
228 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
229 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
230 3
                        static::VINIT => $prefix.'ий',
231 3
                        static::TVORIT => $prefix.'им',
232 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 225 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...
233 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 225 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...
234
                    ];
235
236
                // Ростовская
237 39
                case 'ая':
238 1
                    $prefix = S::name(S::slice($name, 0, -2));
239
                    return [
240 1
                        static::IMENIT => $prefix.'ая',
241 1
                        static::RODIT => $prefix.'ой',
242 1
                        static::DAT => $prefix.'ой',
243 1
                        static::VINIT => $prefix.'ую',
244 1
                        static::TVORIT => $prefix.'ой',
245 1
                        static::PREDLOJ => $prefix.'ой',
246 1
                        static::LOCATIVE => $prefix.'ой',
247
                    ];
248
249
                // Нижняя, Верхняя, Средняя
250 38
                case 'яя':
251
                    $prefix = S::name(S::slice($name, 0, -2));
252
                    return [
253
                        static::IMENIT => $prefix.'яя',
254
                        static::RODIT => $prefix.'ей',
255
                        static::DAT => $prefix.'ей',
256
                        static::VINIT => $prefix.'юю',
257
                        static::TVORIT => $prefix.'ей',
258
                        static::PREDLOJ => $prefix.'ей',
259
                        static::LOCATIVE => $prefix.'ей',
260
                    ];
261
262
                // Россошь
263 38
                case 'шь':
264
                // Пермь, Кемь
265 37
                case 'мь':
266
                // Рязань, Назрань
267 36
                case 'нь':
268
                // Сысерть
269 35
                case 'ть':
270
                // Керчь
271 35
                case 'чь':
272 4
                    $prefix = S::name(S::slice($name, 0, -1));
273
                    return [
274 4
                        static::IMENIT => $prefix.'ь',
275 4
                        static::RODIT => $prefix.'и',
276 4
                        static::DAT => $prefix.'и',
277 4
                        static::VINIT => $prefix.'ь',
278 4
                        static::TVORIT => $prefix.'ью',
279 4
                        static::PREDLOJ => $prefix.'и',
280 4
                        static::LOCATIVE => $prefix.'и',
281
                    ];
282
283
                // Грозный, Благодарный
284 34
                case 'ый':
285 2
                    $prefix = S::name(S::slice($name, 0, -2));
286
                    return [
287 2
                        static::IMENIT => $prefix.'ый',
288 2
                        static::RODIT => $prefix.'ого',
289 2
                        static::DAT => $prefix.'ому',
290 2
                        static::VINIT => $prefix.'ый',
291 2
                        static::TVORIT => $prefix.'ым',
292 2
                        static::PREDLOJ => $prefix.'ом',
293 2
                        static::LOCATIVE => $prefix.'ом',
294
                    ];
295
296
                // Ставрополь, Ярославль, Электросталь
297 32
                case 'ль':
298 2
                    $prefix = S::name(S::slice($name, 0, -1));
299
300 2
                    if ($name === 'электросталь')
301
                        return [
302 1
                            static::IMENIT => $prefix.'ь',
303 1
                            static::RODIT => $prefix.'и',
304 1
                            static::DAT => $prefix.'и',
305 1
                            static::VINIT => $prefix.'ь',
306 1
                            static::TVORIT => $prefix.'ью',
307 1
                            static::PREDLOJ => $prefix.'и',
308 1
                            static::LOCATIVE => $prefix.'и',
309
                        ];
310
311
                    return [
312 1
                        static::IMENIT => $prefix.'ь',
313 1
                        static::RODIT => $prefix.'я',
314 1
                        static::DAT => $prefix.'ю',
315 1
                        static::VINIT => $prefix.'ь',
316 1
                        static::TVORIT => $prefix.'ем',
317 1
                        static::PREDLOJ => $prefix.'е',
318 1
                        static::LOCATIVE => $prefix.'е',
319
                    ];
320
321
                // Тверь, Анадырь
322 30
                case 'рь':
323 2
                    $prefix = S::name(S::slice($name, 0, -1));
324 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 323 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...
325
                    return [
326 2
                        static::IMENIT => $prefix . 'ь',
327 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
328 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
329 2
                        static::VINIT => $prefix . 'ь',
330 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
331 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
332 2
                        static::LOCATIVE => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
333
                    ];
334
335
                // Березники, Ессентуки
336 28
                case 'ки':
337
                // Старые Дороги
338 27
                case 'ги':
339
                // Ушачи, Ивацевичи
340 27
                case 'чи':
341 3
                    $prefix = S::name(S::slice($name, 0, -1));
342
                    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...
343 3
                        static::IMENIT => $prefix . 'и',
344 3
                        static::RODIT => ($name === 'луки'
345 1
                            ? $prefix
346 2
                            : (S::slice($name, -2) === 'чи'
347 1
                                ? $prefix . 'ей'
348 3
                                : $prefix . 'ов')),
349 3
                        static::DAT => $prefix . 'ам',
350 3
                        static::VINIT => $prefix . 'и',
351 3
                        static::TVORIT => $prefix . 'ами',
352 3
                        static::PREDLOJ => $prefix . 'ах',
353 3
                        static::LOCATIVE => $prefix . 'ах',
354
                    ];
355
356
                // Набережные
357 26
                case 'ые':
358
                // Великие
359 26
                case 'ие':
360 2
                    $prefix = S::name(S::slice($name, 0, -1));
361
                    return [
362 2
                        static::IMENIT => $prefix . 'е',
363 2
                        static::RODIT => $prefix . 'х',
364 2
                        static::DAT => $prefix . 'м',
365 2
                        static::VINIT => $prefix . 'е',
366 2
                        static::TVORIT => $prefix . 'ми',
367 2
                        static::PREDLOJ => $prefix . 'х',
368 2
                        static::LOCATIVE => $prefix . 'х',
369
                    ];
370
371
                // Челны
372 25
                case 'ны':
373
                // Мосты
374 24
                case 'ты':
375
                // Столбцы
376 24
                case 'цы':
377 2
                    $prefix = S::name(S::slice($name, 0, -1));
378
                    return [
379 2
                        static::IMENIT => $prefix . 'ы',
380 2
                        static::RODIT => $prefix . 'ов',
381 2
                        static::DAT => $prefix . 'ам',
382 2
                        static::VINIT => $prefix . 'ы',
383 2
                        static::TVORIT => $prefix . 'ами',
384 2
                        static::PREDLOJ => $prefix . 'ах',
385 2
                        static::LOCATIVE => $prefix . 'ах',
386
                    ];
387
388
                // Глубокое
389 23
                case 'ое':
390 1
                    $prefix = S::name(S::slice($name, 0, -2));
391
                    return [
392 1
                        static::IMENIT => $prefix.'ое',
393 1
                        static::RODIT => $prefix.'ого',
394 1
                        static::DAT => $prefix.'ому',
395 1
                        static::VINIT => $prefix.'ое',
396 1
                        static::TVORIT => $prefix.'им',
397 1
                        static::PREDLOJ => $prefix.'ом',
398 1
                        static::LOCATIVE => $prefix.'ом',
399
                    ];
400
401
            }
402
403 22
            switch (S::slice($name, -1)) {
404 22
                case 'р':
405
                    // Бор
406
                    $prefix = S::name(S::slice($name, 0, -1));
407
                    return [
408
                        static::IMENIT => $prefix.'р',
409
                        static::RODIT => $prefix.'ра',
410
                        static::DAT => $prefix.'ру',
411
                        static::VINIT => $prefix.'р',
412
                        static::TVORIT => $prefix.'ром',
413
                        static::PREDLOJ => $prefix.'ре',
414
                        static::LOCATIVE => $prefix.'ру',
415
                    ];
416
417 22
                case 'ы':
418
                    // Чебоксары, Шахты
419 1
                    $prefix = S::name(S::slice($name, 0, -1));
420
                    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...
421 1
                        static::IMENIT => $prefix.'ы',
422 1
                        static::RODIT => $prefix,
423 1
                        static::DAT => $prefix.'ам',
424 1
                        static::VINIT => $prefix.'ы',
425 1
                        static::TVORIT => $prefix.'ами',
426 1
                        static::PREDLOJ => $prefix.'ах',
427 1
                        static::LOCATIVE => $prefix.'ах',
428
                    ];
429
430 21
                case 'я':
431
                    // Азия
432 1
                    $prefix = S::name(S::slice($name, 0, -1));
433
                    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...
434 1
                        static::IMENIT => S::name($name),
435 1
                        static::RODIT => $prefix.'и',
436 1
                        static::DAT => $prefix.'и',
437 1
                        static::VINIT => $prefix.'ю',
438 1
                        static::TVORIT => $prefix.'ей',
439 1
                        static::PREDLOJ => $prefix.'и',
440 1
                        static::LOCATIVE => $prefix.'и',
441
                    ];
442
443 20
                case 'а':
444
                    // Москва, Рига
445 5
                    $prefix = S::name(S::slice($name, 0, -1));
446
                    return [
447 5
                        static::IMENIT => $prefix.'а',
448 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) || static::isHissingConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
449 5
                        static::DAT => $prefix.'е',
450 5
                        static::VINIT => $prefix.'у',
451 5
                        static::TVORIT => $prefix.'ой',
452 5
                        static::PREDLOJ => $prefix.'е',
453 5
                        static::LOCATIVE => $prefix.'е',
454
                    ];
455
456 15
                case 'й':
457
                    // Ишимбай
458 2
                    $prefix = S::name(S::slice($name, 0, -1));
459
                    return [
460 2
                        static::IMENIT => $prefix . 'й',
461 2
                        static::RODIT => $prefix . 'я',
462 2
                        static::DAT => $prefix . 'ю',
463 2
                        static::VINIT => $prefix . 'й',
464 2
                        static::TVORIT => $prefix . 'ем',
465 2
                        static::PREDLOJ => $prefix . 'е',
466 2
                        static::LOCATIVE => $prefix . 'е',
467
                    ];
468
            }
469
470 13
            if (static::isConsonant(S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
471 12
                $runaway_vowels_list = static::getRunAwayVowelsList();
472
473
                // if run-away vowel in name
474 12
                if (isset($runaway_vowels_list[$name])) {
475 4
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
476 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 475 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...
477
                } else {
478 8
                    $prefix = S::name($name);
479
                }
480
481
                // Париж, Валаам, Киев
482
                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...
483 12
                    static::IMENIT => S::name($name),
484 12
                    static::RODIT => $prefix . 'а',
485 12
                    static::DAT => $prefix . 'у',
486 12
                    static::VINIT => S::name($name),
487 12
                    static::TVORIT => $prefix . (static::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
488 12
                    static::PREDLOJ => $prefix . 'е',
489 12
                    static::LOCATIVE => $prefix.($name === 'крым' ? 'у' : 'е'),
490
                ];
491
            }
492
493
            // ов, ово, ёв, ёво, ев, ево, ...
494 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
495 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)) {
496
                // ово, ёво, ...
497 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
498
                    $prefix = S::name(S::slice($name, 0, -1));
499
                }
500
                // ов, её, ...
501 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
502 1
                    $prefix = S::name($name);
503
                } else {
504
                    $prefix = '';
505
                }
506
507
                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...
508 1
                    static::IMENIT => S::name($name),
509 1
                    static::RODIT => $prefix.'а',
510 1
                    static::DAT => $prefix.'у',
511 1
                    static::VINIT => S::name($name),
512 1
                    static::TVORIT => $prefix.'ым',
513 1
                    static::PREDLOJ => $prefix.'е',
514 1
                    static::LOCATIVE => $prefix.'е',
515
                ];
516
            }
517
        }
518
519
        // if no rules matches or name is immutable
520 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
521 3
        return array_fill_keys(
522 3
            [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
523 3
        $name);
524
    }
525
526
    /**
527
     * Получение одной формы (падежа) названия.
528
     * @param string $name  Название
529
     * @param string $case Падеж. Одна из констант {@see \morphos\Russian\Cases} или {@see \morphos\Cases}.
530
     * @see \morphos\Russian\Cases
531
     * @return string
532
     * @throws \Exception
533
     */
534
    public static function getCase($name, $case)
535
    {
536
        $case = static::canonizeCase($case);
537
        $forms = static::getCases($name);
538
        return $forms[$case];
539
    }
540
}
541