Completed
Push — master ( d53204...9a9f15 )
by f
01:39
created

GeographicalNamesInflection   F

Complexity

Total Complexity 70

Size/Duplication

Total Lines 480
Duplicated Lines 20.42 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 88.14%

Importance

Changes 0
Metric Value
dl 98
loc 480
ccs 223
cts 253
cp 0.8814
rs 2.8
c 0
b 0
f 0
wmc 70
lcom 1
cbo 5

4 Methods

Rating   Name   Duplication   Size   Complexity  
A getRunAwayVowelsList() 8 8 2
B isMutable() 0 37 9
F getCases() 90 353 58
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
    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
31
    protected static $immutableNames = [
32
        'алматы',
33
        'сочи',
34
        'гоа',
35
        
36
        // части
37
        'санкт',
38
        'йошкар',
39
        'улан',
40
        'ханты',
41
        'буда',
42
    ];
43
44
    protected static $runawayVowelsExceptions = [
45
        'торжо*к',
46
        'волоче*к',
47
        'орё*л',
48
        'египе*т',
49
        'лунине*ц',
50
        'городо*к',
51
        'новогрудо*к',
52
        'острове*ц',
53
    ];
54
55
    protected static $misspellings = [
56
        'орел' => 'орёл',
57
    ];
58
59
    /**
60
     * @return array|bool
61
     */
62 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...
63
    {
64 12
        $runawayVowelsNormalized = [];
65 12
        foreach (static::$runawayVowelsExceptions as $word) {
66 12
            $runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
67
        }
68 12
        return $runawayVowelsNormalized;
69
    }
70
71
    /**
72
     * Проверяет, склоняемо ли название
73
     * @param string $name Название
74
     * @return bool
75
     */
76 2
    public static function isMutable($name)
77
    {
78 2
        $name = S::lower($name);
79
80
        // // ends with 'ы' or 'и': plural form
81
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
82
        //     return false;
83
84 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableNames, true)) {
85 2
            return false;
86
        }
87
88
        if (strpos($name, ' ') !== false) {
89
            // explode() is not applicable because Geographical unit may have few words
90
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 can also be of type boolean; however, morphos\S::findFirstPosition() 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...
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 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...
91
            $last_part = S::slice($name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 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...
92
                S::findLastPosition($name, ' ') + 1);
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 can also be of type boolean; however, morphos\S::findLastPosition() 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...
93
94
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
95
            // N область, N край, N район, N волость
96
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
97
                || in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
98
                return true;
99
            }
100
101
            // пгт N
102
            if ($first_part === 'пгт')
103
                return false;
104
        }
105
106
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
107
        if (in_array(S::slice($name, -1), ['е', 'о'], true)
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 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...
108
            && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 78 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...
109
            return false;
110
        }
111
        return true;
112
    }
113
114
    /**
115
     * Получение всех форм названия
116
     * @param string $name
117
     * @return array
118
     * @throws \Exception
119
     */
120 45
    public static function getCases($name)
121
    {
122 45
        $name = S::lower($name);
123
124
        // Проверка на неизменяемость и сложное название
125 45
        if (in_array($name, static::$immutableNames, true)) {
126 2
            return array_fill_keys(
127 2
                [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE]
128 2
                , S::name($name));
129
        }
130
131 45
        if (strpos($name, ' ') !== false) {
132 9
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 can also be of type boolean; however, morphos\S::findFirstPosition() 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...
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 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...
133
            // город N, село N, хутор N, пгт N
134 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
135 3
                if ($first_part === 'пгт')
136
                    return array_fill_keys(
137
                        [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
138
                        'пгт '.S::name(S::slice($name, 4)));
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 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...
139
140 3
                if ($first_part === 'республика') {
141 1
                    $prefix = array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part));
142
                } else {
143 2
                    $prefix = NounDeclension::getCases($first_part);
0 ignored issues
show
Security Bug introduced by
It seems like $first_part defined by \morphos\S::slice($name,...stPosition($name, ' ')) on line 132 can also be of type false; however, morphos\Russian\NounDeclension::getCases() 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...
144
                }
145 3
                $prefix[Cases::LOCATIVE] = $prefix[Cases::PREDLOJ];
146
147 3
                return static::composeCasesFromWords([$prefix,
148 3
                    array_fill_keys(
149 3
                        array_merge(static::getAllCases(), [\morphos\Russian\Cases::LOCATIVE]),
150 3
                        S::name(S::slice($name, S::length($first_part) + 1)))
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 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...
151
                ]);
152
            }
153
154 6
            $last_part = S::slice($name,
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 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...
155 6
                S::findLastPosition($name, ' ') + 1);
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 can also be of type boolean; however, morphos\S::findLastPosition() 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...
156
            // N область, N край
157 6
            if (in_array($last_part, ['край', 'область', 'район', 'волость'], true)) {
158 2
                $last_part_cases = NounDeclension::getCases($last_part);
0 ignored issues
show
Security Bug introduced by
It seems like $last_part defined by \morphos\S::slice($name,...sition($name, ' ') + 1) on line 154 can also be of type false; however, morphos\Russian\NounDeclension::getCases() 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...
159 2
                $last_part_cases[Cases::LOCATIVE] = $last_part_cases[Cases::PREDLOJ];
160 2
                return static::composeCasesFromWords(
161
                    [
162 2
                        static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))),
0 ignored issues
show
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 can also be of type boolean; however, morphos\S::findLastPosition() 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...
Bug introduced by
It seems like $name defined by \morphos\S::lower($name) on line 122 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...
Security Bug introduced by
It seems like \morphos\S::slice($name,...stPosition($name, ' ')) 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...
163 2
                        $last_part_cases,
164
                    ]);
165
            }
166
        }
167
168
        // Сложное название с разделителем
169 42
        foreach (static::$delimiters as $delimiter) {
170 42
            if (strpos($name, $delimiter) !== false) {
171 7
                $parts = explode($delimiter, $name);
172 7
                $result = [];
173 7
                foreach ($parts as $i => $part) {
174 7
                    $result[$i] = static::getCases($part);
175
                }
176 42
                return static::composeCasesFromWords($result, $delimiter);
177
            }
178
        }
179
180
        // Исправление ошибок
181 42
        if (array_key_exists($name, static::$misspellings)) {
182
            $name = static::$misspellings[$name];
183
        }
184
185
        // Само склонение
186 42
        if (!in_array($name, static::$abbreviations, true)) {
187 40
            switch (S::slice($name, -2)) {
188
                // Нижний, Русский
189 40
                case 'ий':
190 3
                    $prefix = S::name(S::slice($name, 0, -2));
191
                    return [
192 3
                        static::IMENIT => $prefix.'ий',
193 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
194 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
195 3
                        static::VINIT => $prefix.'ий',
196 3
                        static::TVORIT => $prefix.'им',
197 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 190 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...
198 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 190 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...
199
                    ];
200
201
                // Ростовская
202 39 View Code Duplication
                case 'ая':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
203 1
                    $prefix = S::name(S::slice($name, 0, -2));
204
                    return [
205 1
                        static::IMENIT => $prefix.'ая',
206 1
                        static::RODIT => $prefix.'ой',
207 1
                        static::DAT => $prefix.'ой',
208 1
                        static::VINIT => $prefix.'ую',
209 1
                        static::TVORIT => $prefix.'ой',
210 1
                        static::PREDLOJ => $prefix.'ой',
211 1
                        static::LOCATIVE => $prefix.'ой',
212
                    ];
213
214
                // Россошь
215 38
                case 'шь':
216
                // Пермь, Кемь
217 37
                case 'мь':
218
                // Рязань, Назрань
219 36
                case 'нь':
220
                // Керчь
221 35 View Code Duplication
                case 'чь':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
222 4
                    $prefix = S::name(S::slice($name, 0, -1));
223
                    return [
224 4
                        static::IMENIT => $prefix.'ь',
225 4
                        static::RODIT => $prefix.'и',
226 4
                        static::DAT => $prefix.'и',
227 4
                        static::VINIT => $prefix.'ь',
228 4
                        static::TVORIT => $prefix.'ью',
229 4
                        static::PREDLOJ => $prefix.'и',
230 4
                        static::LOCATIVE => $prefix.'и',
231
                    ];
232
233
                // Грозный, Благодарный
234 34 View Code Duplication
                case 'ый':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
235 2
                    $prefix = S::name(S::slice($name, 0, -2));
236
                    return [
237 2
                        static::IMENIT => $prefix.'ый',
238 2
                        static::RODIT => $prefix.'ого',
239 2
                        static::DAT => $prefix.'ому',
240 2
                        static::VINIT => $prefix.'ый',
241 2
                        static::TVORIT => $prefix.'ым',
242 2
                        static::PREDLOJ => $prefix.'ом',
243 2
                        static::LOCATIVE => $prefix.'ом',
244
                    ];
245
246
                // Ставрополь, Ярославль, Электросталь
247 32
                case 'ль':
248 2
                    $prefix = S::name(S::slice($name, 0, -1));
249
250 2
                    if ($name === 'электросталь')
251
                        return [
252 1
                            static::IMENIT => $prefix.'ь',
253 1
                            static::RODIT => $prefix.'и',
254 1
                            static::DAT => $prefix.'и',
255 1
                            static::VINIT => $prefix.'ь',
256 1
                            static::TVORIT => $prefix.'ью',
257 1
                            static::PREDLOJ => $prefix.'и',
258 1
                            static::LOCATIVE => $prefix.'и',
259
                        ];
260
261
                    return [
262 1
                        static::IMENIT => $prefix.'ь',
263 1
                        static::RODIT => $prefix.'я',
264 1
                        static::DAT => $prefix.'ю',
265 1
                        static::VINIT => $prefix.'ь',
266 1
                        static::TVORIT => $prefix.'ем',
267 1
                        static::PREDLOJ => $prefix.'е',
268 1
                        static::LOCATIVE => $prefix.'е',
269
                    ];
270
271
                // Тверь, Анадырь
272 30
                case 'рь':
273 2
                    $prefix = S::name(S::slice($name, 0, -1));
274 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 273 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...
275
                    return [
276 2
                        static::IMENIT => $prefix . 'ь',
277 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
278 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
279 2
                        static::VINIT => $prefix . 'ь',
280 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
281 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
282 2
                        static::LOCATIVE => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
283
                    ];
284
285
                // Березники, Ессентуки
286 28
                case 'ки':
287
                // Старые Дороги
288 27
                case 'ги':
289
                // Ушачи, Ивацевичи
290 27
                case 'чи':
291 3
                    $prefix = S::name(S::slice($name, 0, -1));
292
                    return [
293 3
                        static::IMENIT => $prefix . 'и',
294 3
                        static::RODIT => ($name === 'луки'
295 1
                            ? $prefix
296 2
                            : (S::slice($name, -2) === 'чи'
297 1
                                ? $prefix . 'ей'
298 3
                                : $prefix . 'ов')),
299 3
                        static::DAT => $prefix . 'ам',
300 3
                        static::VINIT => $prefix . 'и',
301 3
                        static::TVORIT => $prefix . 'ами',
302 3
                        static::PREDLOJ => $prefix . 'ах',
303 3
                        static::LOCATIVE => $prefix . 'ах',
304
                    ];
305
306
                // Набережные
307 26
                case 'ые':
308
                // Великие
309 26 View Code Duplication
                case 'ие':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
310 2
                    $prefix = S::name(S::slice($name, 0, -1));
311
                    return [
312 2
                        static::IMENIT => $prefix . 'е',
313 2
                        static::RODIT => $prefix . 'х',
314 2
                        static::DAT => $prefix . 'м',
315 2
                        static::VINIT => $prefix . 'е',
316 2
                        static::TVORIT => $prefix . 'ми',
317 2
                        static::PREDLOJ => $prefix . 'х',
318 2
                        static::LOCATIVE => $prefix . 'х',
319
                    ];
320
321
                // Челны
322 25
                case 'ны':
323
                // Мосты
324 24
                case 'ты':
325
                // Столбцы
326 24 View Code Duplication
                case 'цы':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327 2
                    $prefix = S::name(S::slice($name, 0, -1));
328
                    return [
329 2
                        static::IMENIT => $prefix . 'ы',
330 2
                        static::RODIT => $prefix . 'ов',
331 2
                        static::DAT => $prefix . 'ам',
332 2
                        static::VINIT => $prefix . 'ы',
333 2
                        static::TVORIT => $prefix . 'ами',
334 2
                        static::PREDLOJ => $prefix . 'ах',
335 2
                        static::LOCATIVE => $prefix . 'ах',
336
                    ];
337
338
                // Глубокое
339 23 View Code Duplication
                case 'ое':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340 1
                    $prefix = S::name(S::slice($name, 0, -2));
341
                    return [
342 1
                        static::IMENIT => $prefix.'ое',
343 1
                        static::RODIT => $prefix.'ого',
344 1
                        static::DAT => $prefix.'ому',
345 1
                        static::VINIT => $prefix.'ое',
346 1
                        static::TVORIT => $prefix.'им',
347 1
                        static::PREDLOJ => $prefix.'ом',
348 1
                        static::LOCATIVE => $prefix.'ом',
349
                    ];
350
351
            }
352
353 22
            switch (S::slice($name, -1)) {
354 22 View Code Duplication
                case 'р':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
355
                    // Бор
356
                    $prefix = S::name(S::slice($name, 0, -1));
357
                    return [
358
                        static::IMENIT => $prefix.'р',
359
                        static::RODIT => $prefix.'ра',
360
                        static::DAT => $prefix.'ру',
361
                        static::VINIT => $prefix.'р',
362
                        static::TVORIT => $prefix.'ром',
363
                        static::PREDLOJ => $prefix.'ре',
364
                        static::LOCATIVE => $prefix.'ру',
365
                    ];
366
367 22 View Code Duplication
                case 'ы':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
368
                    // Чебоксары, Шахты
369 1
                    $prefix = S::name(S::slice($name, 0, -1));
370
                    return [
371 1
                        static::IMENIT => $prefix.'ы',
372 1
                        static::RODIT => $prefix,
373 1
                        static::DAT => $prefix.'ам',
374 1
                        static::VINIT => $prefix.'ы',
375 1
                        static::TVORIT => $prefix.'ами',
376 1
                        static::PREDLOJ => $prefix.'ах',
377 1
                        static::LOCATIVE => $prefix.'ах',
378
                    ];
379
380 21
                case 'я':
381
                    // Азия
382 1
                    $prefix = S::name(S::slice($name, 0, -1));
383
                    return [
384 1
                        static::IMENIT => S::name($name),
385 1
                        static::RODIT => $prefix.'и',
386 1
                        static::DAT => $prefix.'и',
387 1
                        static::VINIT => $prefix.'ю',
388 1
                        static::TVORIT => $prefix.'ей',
389 1
                        static::PREDLOJ => $prefix.'и',
390 1
                        static::LOCATIVE => $prefix.'и',
391
                    ];
392
393 20
                case 'а':
394
                    // Москва, Рига
395 5
                    $prefix = S::name(S::slice($name, 0, -1));
396
                    return [
397 5
                        static::IMENIT => $prefix.'а',
398 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) || static::isHissingConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
399 5
                        static::DAT => $prefix.'е',
400 5
                        static::VINIT => $prefix.'у',
401 5
                        static::TVORIT => $prefix.'ой',
402 5
                        static::PREDLOJ => $prefix.'е',
403 5
                        static::LOCATIVE => $prefix.'е',
404
                    ];
405
406 15
                case 'й':
407
                    // Ишимбай
408 2
                    $prefix = S::name(S::slice($name, 0, -1));
409
                    return [
410 2
                        static::IMENIT => $prefix . 'й',
411 2
                        static::RODIT => $prefix . 'я',
412 2
                        static::DAT => $prefix . 'ю',
413 2
                        static::VINIT => $prefix . 'й',
414 2
                        static::TVORIT => $prefix . 'ем',
415 2
                        static::PREDLOJ => $prefix . 'е',
416 2
                        static::LOCATIVE => $prefix . 'е',
417
                    ];
418
            }
419
420 13
            if (static::isConsonant(S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
421 12
                $runaway_vowels_list = static::getRunAwayVowelsList();
422
423
                // if run-away vowel in name
424 12
                if (isset($runaway_vowels_list[$name])) {
425 4
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
426 4
                    $prefix = S::name(S::slice($name, 0, $runaway_vowel_offset) . S::slice($name, $runaway_vowel_offset + 1));
427
                } else {
428 8
                    $prefix = S::name($name);
429
                }
430
431
                // Париж, Валаам, Киев
432
                return [
433 12
                    static::IMENIT => S::name($name),
434 12
                    static::RODIT => $prefix . 'а',
435 12
                    static::DAT => $prefix . 'у',
436 12
                    static::VINIT => S::name($name),
437 12
                    static::TVORIT => $prefix . (static::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
438 12
                    static::PREDLOJ => $prefix . 'е',
439 12
                    static::LOCATIVE => $prefix.($name === 'крым' ? 'у' : 'е'),
440
                ];
441
            }
442
443
            // ов, ово, ёв, ёво, ев, ево, ...
444 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
445 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)) {
446
                // ово, ёво, ...
447 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
448
                    $prefix = S::name(S::slice($name, 0, -1));
449
                }
450
                // ов, её, ...
451 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
452 1
                    $prefix = S::name($name);
453
                }
454
455
                return [
456 1
                    static::IMENIT => S::name($name),
457 1
                    static::RODIT => $prefix.'а',
458 1
                    static::DAT => $prefix.'у',
459 1
                    static::VINIT => S::name($name),
460 1
                    static::TVORIT => $prefix.'ым',
461 1
                    static::PREDLOJ => $prefix.'е',
462 1
                    static::LOCATIVE => $prefix.'е',
463
                ];
464
            }
465
        }
466
467
        // if no rules matches or name is immutable
468 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
469 3
        return array_fill_keys(
470 3
            [static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ, static::LOCATIVE],
471 3
        $name);
472
    }
473
474
    /**
475
     * Получение одной формы (падежа) названия.
476
     * @param string $name  Название
477
     * @param integer $case Падеж. Одна из констант \morphos\Russian\Cases или \morphos\Cases.
478
     * @see \morphos\Russian\Cases
479
     * @return string
480
     * @throws \Exception
481
     */
482
    public static function getCase($name, $case)
483
    {
484
        $case = static::canonizeCase($case);
485
        $forms = static::getCases($name);
486
        return $forms[$case];
487
    }
488
}
489